mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-12 07:14:05 +01:00
remove old ContactsActivity and use ContactsActivityCompose
for ConversationInfoActivity, the old ContactsActivity was still used to add participants. This is now replaced by the ContactsActivityCompose, so ContactsActivity is now deleted Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
parent
d6d7a11573
commit
1c7d84f90b
@ -246,10 +246,6 @@
|
||||
android:name=".conversationinfoedit.ConversationInfoEditActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".contacts.ContactsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".openconversations.ListOpenConversationsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
@ -1,900 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.contacts
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.adapters.items.ContactItem
|
||||
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.conversation.CreateConversationDialogFragment
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityContactsBinding
|
||||
import com.nextcloud.talk.events.EventStatus
|
||||
import com.nextcloud.talk.events.OpenConversationEvent
|
||||
import com.nextcloud.talk.jobs.AddParticipantsToConversation
|
||||
import com.nextcloud.talk.models.RetrofitBucket
|
||||
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
|
||||
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
|
||||
import com.nextcloud.talk.models.json.conversations.ConversationEnums
|
||||
import com.nextcloud.talk.models.json.conversations.RoomOverall
|
||||
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.CapabilitiesUtil
|
||||
import com.nextcloud.talk.utils.SpreedFeatures
|
||||
import com.nextcloud.talk.utils.UserIdUtils.getIdForUser
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import okhttp3.ResponseBody
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.parceler.Parcels
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ContactsActivity :
|
||||
BaseActivity(),
|
||||
SearchView.OnQueryTextListener,
|
||||
FlexibleAdapter.OnItemClickListener {
|
||||
private lateinit var binding: ActivityContactsBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
private var credentials: String? = null
|
||||
private var currentUser: User? = null
|
||||
private var contactsQueryDisposable: Disposable? = null
|
||||
private var cacheQueryDisposable: Disposable? = null
|
||||
private var adapter: FlexibleAdapter<*>? = null
|
||||
private var contactItems: MutableList<AbstractFlexibleItem<*>>? = null
|
||||
private var layoutManager: SmoothScrollLinearLayoutManager? = null
|
||||
private var searchItem: MenuItem? = null
|
||||
private var searchView: SearchView? = null
|
||||
private var isNewConversationView = false
|
||||
private var isPublicCall = false
|
||||
private var userHeaderItems: HashMap<String, GenericTextHeaderItem> = HashMap<String, GenericTextHeaderItem>()
|
||||
private var alreadyFetching = false
|
||||
private var doneMenuItem: MenuItem? = null
|
||||
private var selectedUserIds: MutableSet<String> = HashSet()
|
||||
private var selectedGroupIds: MutableSet<String> = HashSet()
|
||||
private var selectedCircleIds: MutableSet<String> = HashSet()
|
||||
private var selectedEmails: MutableSet<String> = HashSet()
|
||||
private var existingParticipants: List<String>? = null
|
||||
private var isAddingParticipantsView = false
|
||||
private var conversationToken: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
binding = ActivityContactsBinding.inflate(layoutInflater)
|
||||
setupActionBar()
|
||||
setContentView(binding.root)
|
||||
setupSystemColors()
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (adapter != null) {
|
||||
adapter?.onRestoreInstanceState(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
existingParticipants = ArrayList()
|
||||
if (intent.hasExtra(BundleKeys.KEY_NEW_CONVERSATION)) {
|
||||
// adding a new conversation, setting a flag.
|
||||
isNewConversationView = true
|
||||
} else if (intent.hasExtra(BundleKeys.KEY_ADD_PARTICIPANTS)) {
|
||||
// adding the participants in the conversation also opens this activity, setting a flag for it.
|
||||
isAddingParticipantsView = true
|
||||
conversationToken = intent.getStringExtra(BundleKeys.KEY_TOKEN)
|
||||
if (intent.hasExtra(BundleKeys.KEY_EXISTING_PARTICIPANTS)) {
|
||||
existingParticipants = intent.getStringArrayListExtra(BundleKeys.KEY_EXISTING_PARTICIPANTS)
|
||||
}
|
||||
}
|
||||
selectedUserIds = HashSet()
|
||||
selectedGroupIds = HashSet()
|
||||
selectedEmails = HashSet()
|
||||
selectedCircleIds = HashSet()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (isNewConversationView) {
|
||||
toggleConversationPrivacyLayout(!isPublicCall)
|
||||
}
|
||||
if (isAddingParticipantsView) {
|
||||
binding.callHeaderLayout.visibility = View.GONE
|
||||
binding.listOpenConversations.visibility = View.GONE
|
||||
} else {
|
||||
binding.listOpenConversations.setOnClickListener {
|
||||
listOpenConversations()
|
||||
}
|
||||
binding.callHeaderLayout.setOnClickListener {
|
||||
toggleCallHeader()
|
||||
}
|
||||
}
|
||||
|
||||
currentUser = currentUserProvider.currentUser.blockingGet()
|
||||
if (currentUser != null) {
|
||||
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
|
||||
}
|
||||
if (adapter == null) {
|
||||
contactItems = ArrayList<AbstractFlexibleItem<*>>()
|
||||
adapter = FlexibleAdapter(contactItems, this, false)
|
||||
if (currentUser != null) {
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
setupAdapter()
|
||||
prepareViews()
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(binding.contactsToolbar)
|
||||
binding.contactsToolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent, null)))
|
||||
supportActionBar?.title = when {
|
||||
isAddingParticipantsView -> {
|
||||
resources!!.getString(R.string.nc_add_participants)
|
||||
}
|
||||
|
||||
isNewConversationView -> {
|
||||
resources!!.getString(R.string.nc_select_participants)
|
||||
}
|
||||
|
||||
else -> {
|
||||
resources!!.getString(R.string.nc_app_product_name)
|
||||
}
|
||||
}
|
||||
viewThemeUtils.material.themeToolbar(binding.contactsToolbar)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(bundle: Bundle) {
|
||||
super.onSaveInstanceState(bundle)
|
||||
adapter?.onSaveInstanceState(bundle)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.menu_contacts, menu)
|
||||
searchItem = menu.findItem(R.id.action_search)
|
||||
doneMenuItem = menu.findItem(R.id.contacts_selection_done)
|
||||
initSearchView()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
if (searchItem != null) {
|
||||
binding.titleTextView.let {
|
||||
viewThemeUtils.platform.colorToolbarMenuIcon(
|
||||
it.context,
|
||||
searchItem!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
checkAndHandleDoneMenuItem()
|
||||
if (adapter?.hasFilter() == true) {
|
||||
searchItem!!.expandActionView()
|
||||
searchView!!.setQuery(adapter!!.getFilter(String::class.java) as CharSequence, false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.contacts_selection_done -> {
|
||||
selectionDone()
|
||||
true
|
||||
}
|
||||
|
||||
else -> {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupAdapter() {
|
||||
adapter?.setNotifyChangeOfUnfilteredItems(true)?.mode = SelectableAdapter.Mode.MULTI
|
||||
adapter?.setStickyHeaderElevation(HEADER_ELEVATION)
|
||||
?.setUnlinkAllItemsOnRemoveHeaders(true)
|
||||
?.setDisplayHeadersAtStartUp(true)
|
||||
?.setStickyHeaders(true)
|
||||
adapter?.addListener(this)
|
||||
}
|
||||
|
||||
private fun selectionDone() {
|
||||
if (isAddingParticipantsView) {
|
||||
// add participants in the view
|
||||
addParticipantsToConversation()
|
||||
} else {
|
||||
// if there is only 1 participant, directly add him while creating room (which can only add 'one')
|
||||
if (!isPublicCall && selectedCircleIds.size + selectedGroupIds.size + selectedUserIds.size == 1) {
|
||||
val userId: String
|
||||
var sourceType: String? = null
|
||||
var roomType = "1"
|
||||
when {
|
||||
selectedGroupIds.size == 1 -> {
|
||||
roomType = "2"
|
||||
userId = selectedGroupIds.iterator().next()
|
||||
}
|
||||
|
||||
selectedCircleIds.size == 1 -> {
|
||||
roomType = "2"
|
||||
sourceType = "circles"
|
||||
userId = selectedCircleIds.iterator().next()
|
||||
}
|
||||
|
||||
else -> {
|
||||
userId = selectedUserIds.iterator().next()
|
||||
}
|
||||
}
|
||||
createRoom(roomType, sourceType, userId)
|
||||
|
||||
// if there are more participants to add, ask for roomName and add them one after another
|
||||
} else {
|
||||
val roomType: ConversationEnums.ConversationType = if (isPublicCall) {
|
||||
ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
|
||||
} else {
|
||||
ConversationEnums.ConversationType.ROOM_GROUP_CALL
|
||||
}
|
||||
val userIdsArray = ArrayList(selectedUserIds)
|
||||
val groupIdsArray = ArrayList(selectedGroupIds)
|
||||
val emailsArray = ArrayList(selectedEmails)
|
||||
val circleIdsArray = ArrayList(selectedCircleIds)
|
||||
|
||||
val createConversationDialog = CreateConversationDialogFragment.newInstance(
|
||||
userIdsArray,
|
||||
groupIdsArray,
|
||||
emailsArray,
|
||||
circleIdsArray,
|
||||
Parcels.wrap(roomType)
|
||||
)
|
||||
createConversationDialog.show(
|
||||
supportFragmentManager,
|
||||
TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRoom(roomType: String, sourceType: String?, userId: String) {
|
||||
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
|
||||
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
|
||||
apiVersion,
|
||||
currentUser!!.baseUrl!!,
|
||||
roomType,
|
||||
sourceType,
|
||||
userId,
|
||||
null
|
||||
)
|
||||
ncApi.createRoom(
|
||||
credentials,
|
||||
retrofitBucket.url,
|
||||
retrofitBucket.queryMap
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
|
||||
// bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
|
||||
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(bundle)
|
||||
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(chatIntent)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun addParticipantsToConversation() {
|
||||
val userIdsArray: Array<String> = selectedUserIds.toTypedArray()
|
||||
val groupIdsArray: Array<String> = selectedGroupIds.toTypedArray()
|
||||
val emailsArray: Array<String> = selectedEmails.toTypedArray()
|
||||
val circleIdsArray: Array<String> = selectedCircleIds.toTypedArray()
|
||||
val data = Data.Builder()
|
||||
data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, currentUser!!.id!!)
|
||||
data.putString(BundleKeys.KEY_TOKEN, conversationToken)
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_USERS, userIdsArray)
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_GROUPS, groupIdsArray)
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_EMAILS, emailsArray)
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_CIRCLES, circleIdsArray)
|
||||
val addParticipantsToConversationWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(
|
||||
AddParticipantsToConversation::class.java
|
||||
).setInputData(data.build()).build()
|
||||
WorkManager.getInstance().enqueue(addParticipantsToConversationWorker)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(addParticipantsToConversationWorker.id)
|
||||
.observeForever { workInfo: WorkInfo? ->
|
||||
if (workInfo != null) {
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
Log.d(TAG, "running AddParticipantsToConversation")
|
||||
}
|
||||
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
Log.d(TAG, "success AddParticipantsToConversation")
|
||||
|
||||
eventBus.post(
|
||||
EventStatus(
|
||||
getIdForUser(currentUser),
|
||||
EventStatus.EventType.PARTICIPANTS_UPDATE,
|
||||
true
|
||||
)
|
||||
)
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED -> {
|
||||
Log.d(TAG, "failed AddParticipantsToConversation")
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
val searchManager: SearchManager? = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
|
||||
if (searchItem != null) {
|
||||
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
|
||||
viewThemeUtils.talk.themeSearchView(searchView!!)
|
||||
searchView!!.maxWidth = Int.MAX_VALUE
|
||||
searchView!!.inputType = InputType.TYPE_TEXT_VARIATION_FILTER
|
||||
var imeOptions: Int = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
||||
if (appPreferences.isKeyboardIncognito == true) {
|
||||
imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
}
|
||||
searchView!!.imeOptions = imeOptions
|
||||
searchView!!.queryHint = resources!!.getString(R.string.nc_search)
|
||||
if (searchManager != null) {
|
||||
searchView!!.setSearchableInfo(searchManager.getSearchableInfo(componentName))
|
||||
}
|
||||
searchView!!.setOnQueryTextListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchData() {
|
||||
dispose(null)
|
||||
alreadyFetching = true
|
||||
userHeaderItems = HashMap()
|
||||
val query = adapter!!.getFilter(String::class.java)
|
||||
val retrofitBucket: RetrofitBucket =
|
||||
ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl!!, query)
|
||||
val modifiedQueryMap: HashMap<String, Any?> = HashMap(retrofitBucket.queryMap)
|
||||
modifiedQueryMap["limit"] = CONTACTS_BATCH_SIZE
|
||||
if (isAddingParticipantsView) {
|
||||
modifiedQueryMap["itemId"] = conversationToken
|
||||
}
|
||||
val shareTypesList: ArrayList<String> = ArrayList()
|
||||
// users
|
||||
shareTypesList.add("0")
|
||||
if (!isAddingParticipantsView) {
|
||||
// groups
|
||||
shareTypesList.add("1")
|
||||
} else if (CapabilitiesUtil.hasSpreedFeatureCapability(
|
||||
currentUser?.capabilities?.spreedCapability!!,
|
||||
SpreedFeatures.INVITE_GROUPS_AND_MAILS
|
||||
)
|
||||
) {
|
||||
// groups
|
||||
shareTypesList.add("1")
|
||||
// emails
|
||||
shareTypesList.add("4")
|
||||
}
|
||||
if (CapabilitiesUtil.hasSpreedFeatureCapability(
|
||||
currentUser?.capabilities?.spreedCapability!!,
|
||||
SpreedFeatures.CIRCLES_SUPPORT
|
||||
)
|
||||
) {
|
||||
// circles
|
||||
shareTypesList.add("7")
|
||||
}
|
||||
modifiedQueryMap["shareTypes[]"] = shareTypesList
|
||||
ncApi.getContactsWithSearchParam(
|
||||
credentials,
|
||||
retrofitBucket.url,
|
||||
shareTypesList,
|
||||
modifiedQueryMap
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.retry(RETRIES)
|
||||
.subscribe(object : Observer<ResponseBody> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
contactsQueryDisposable = d
|
||||
}
|
||||
|
||||
override fun onNext(responseBody: ResponseBody) {
|
||||
// getting contacts
|
||||
val newUserItemList = processAutocompleteUserList(responseBody)
|
||||
|
||||
userHeaderItems = HashMap()
|
||||
// getting the contact list from the endpoints.
|
||||
contactItems!!.addAll(newUserItemList)
|
||||
|
||||
sortUserItems(newUserItemList)
|
||||
|
||||
if (newUserItemList.size > 0) {
|
||||
adapter?.updateDataSet(newUserItemList as List<Nothing>?)
|
||||
} else {
|
||||
adapter?.filterItems()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
dispose(contactsQueryDisposable)
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
dispose(contactsQueryDisposable)
|
||||
alreadyFetching = false
|
||||
disengageProgressBar()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun processAutocompleteUserList(responseBody: ResponseBody): MutableList<AbstractFlexibleItem<*>> {
|
||||
try {
|
||||
val autocompleteOverall: AutocompleteOverall = LoganSquare.parse(
|
||||
responseBody.string(),
|
||||
AutocompleteOverall::class.java
|
||||
)
|
||||
val autocompleteUsersList: ArrayList<AutocompleteUser> = ArrayList()
|
||||
autocompleteUsersList.addAll(autocompleteOverall.ocs!!.data!!)
|
||||
return processAutocompleteUserList(autocompleteUsersList)
|
||||
} catch (ioe: IOException) {
|
||||
Log.e(TAG, "Parsing response body failed while getting contacts", ioe)
|
||||
}
|
||||
|
||||
return ArrayList()
|
||||
}
|
||||
|
||||
private fun processAutocompleteUserList(
|
||||
autocompleteUsersList: ArrayList<AutocompleteUser>
|
||||
): MutableList<AbstractFlexibleItem<*>> {
|
||||
var participant: Participant
|
||||
val actorTypeConverter = EnumActorTypeConverter()
|
||||
val newUserItemList: MutableList<AbstractFlexibleItem<*>> = ArrayList()
|
||||
for (autocompleteUser in autocompleteUsersList) {
|
||||
if (autocompleteUser.id != null &&
|
||||
autocompleteUser.id != currentUser!!.userId &&
|
||||
!existingParticipants!!.contains(autocompleteUser.id)
|
||||
) {
|
||||
participant = createParticipant(autocompleteUser, actorTypeConverter)
|
||||
val headerTitle = getHeaderTitle(participant)
|
||||
var genericTextHeaderItem: GenericTextHeaderItem
|
||||
if (!userHeaderItems.containsKey(headerTitle)) {
|
||||
genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
|
||||
userHeaderItems.put(headerTitle, genericTextHeaderItem)
|
||||
}
|
||||
val newContactItem = ContactItem(
|
||||
participant,
|
||||
currentUser!!,
|
||||
userHeaderItems[headerTitle],
|
||||
viewThemeUtils
|
||||
)
|
||||
if (!contactItems!!.contains(newContactItem)) {
|
||||
newUserItemList.add(newContactItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newUserItemList
|
||||
}
|
||||
|
||||
// this function displays the title of the contacts activity
|
||||
private fun getHeaderTitle(participant: Participant): String {
|
||||
return when {
|
||||
participant.calculatedActorType == Participant.ActorType.GROUPS -> {
|
||||
resources!!.getString(R.string.nc_groups)
|
||||
}
|
||||
|
||||
participant.calculatedActorType == Participant.ActorType.CIRCLES -> {
|
||||
resources!!.getString(R.string.nc_teams)
|
||||
}
|
||||
|
||||
else -> {
|
||||
participant.displayName!!.substring(0, 1).uppercase(Locale.getDefault())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createParticipant(
|
||||
autocompleteUser: AutocompleteUser,
|
||||
actorTypeConverter: EnumActorTypeConverter
|
||||
): Participant {
|
||||
val participant = Participant()
|
||||
participant.actorId = autocompleteUser.id
|
||||
participant.actorType = actorTypeConverter.getFromString(autocompleteUser.source)
|
||||
participant.displayName = autocompleteUser.label
|
||||
|
||||
return participant
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun sortUserItems(newUserItemList: MutableList<AbstractFlexibleItem<*>>) {
|
||||
newUserItemList.sortWith sort@{ o1: AbstractFlexibleItem<*>, o2: AbstractFlexibleItem<*> ->
|
||||
val firstName: String = if (o1 is ContactItem) {
|
||||
o1.model.displayName!!
|
||||
} else {
|
||||
(o1 as GenericTextHeaderItem).model
|
||||
}
|
||||
val secondName: String = if (o2 is ContactItem) {
|
||||
o2.model.displayName!!
|
||||
} else {
|
||||
(o2 as GenericTextHeaderItem).model
|
||||
}
|
||||
if (o1 is ContactItem && o2 is ContactItem) {
|
||||
val firstSource: Participant.ActorType = o1.model.actorType!!
|
||||
val secondSource: Participant.ActorType = o2.model.actorType!!
|
||||
if (firstSource == secondSource) {
|
||||
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
||||
}
|
||||
|
||||
// First users
|
||||
if (Participant.ActorType.USERS == firstSource) {
|
||||
return@sort -1
|
||||
} else if (Participant.ActorType.USERS == secondSource) {
|
||||
return@sort 1
|
||||
}
|
||||
|
||||
// Then groups
|
||||
if (Participant.ActorType.GROUPS == firstSource) {
|
||||
return@sort -1
|
||||
} else if (Participant.ActorType.GROUPS == secondSource) {
|
||||
return@sort 1
|
||||
}
|
||||
|
||||
// Then circles
|
||||
if (Participant.ActorType.CIRCLES == firstSource) {
|
||||
return@sort -1
|
||||
} else if (Participant.ActorType.CIRCLES == secondSource) {
|
||||
return@sort 1
|
||||
}
|
||||
|
||||
// Otherwise fall back to name sorting
|
||||
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
||||
}
|
||||
firstName.compareTo(secondName, ignoreCase = true)
|
||||
}
|
||||
|
||||
contactItems?.sortWith sort@{ o1: AbstractFlexibleItem<*>, o2: AbstractFlexibleItem<*> ->
|
||||
val firstName: String = if (o1 is ContactItem) {
|
||||
o1.model.displayName!!
|
||||
} else {
|
||||
(o1 as GenericTextHeaderItem).model
|
||||
}
|
||||
val secondName: String = if (o2 is ContactItem) {
|
||||
o2.model.displayName!!
|
||||
} else {
|
||||
(o2 as GenericTextHeaderItem).model
|
||||
}
|
||||
if (o1 is ContactItem && o2 is ContactItem) {
|
||||
if (Participant.ActorType.GROUPS == o1.model.actorType &&
|
||||
Participant.ActorType.GROUPS == o2.model.actorType
|
||||
) {
|
||||
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
||||
} else if (Participant.ActorType.GROUPS == o1.model.actorType) {
|
||||
return@sort -1
|
||||
} else if (Participant.ActorType.GROUPS == o2.model.actorType) {
|
||||
return@sort 1
|
||||
}
|
||||
}
|
||||
firstName.compareTo(secondName, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareViews() {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(this)
|
||||
binding.contactsRv.layoutManager = layoutManager
|
||||
binding.contactsRv.setHasFixedSize(true)
|
||||
binding.contactsRv.adapter = adapter
|
||||
|
||||
binding.listOpenConversationsImage.background?.setColorFilter(
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null),
|
||||
PorterDuff.Mode.SRC_IN
|
||||
)
|
||||
|
||||
binding.let {
|
||||
viewThemeUtils.platform.colorImageViewBackgroundAndIcon(it.publicCallLink)
|
||||
}
|
||||
disengageProgressBar()
|
||||
}
|
||||
|
||||
private fun disengageProgressBar() {
|
||||
if (!alreadyFetching) {
|
||||
binding.contactsRv.visibility = View.VISIBLE
|
||||
binding.loadingContent.visibility = View.GONE
|
||||
binding.root.visibility = View.VISIBLE
|
||||
if (isNewConversationView) {
|
||||
binding.callHeaderLayout.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispose(disposable: Disposable?) {
|
||||
if (disposable != null && !disposable.isDisposed) {
|
||||
disposable.dispose()
|
||||
} else if (disposable == null) {
|
||||
if (contactsQueryDisposable != null && !contactsQueryDisposable!!.isDisposed) {
|
||||
contactsQueryDisposable!!.dispose()
|
||||
contactsQueryDisposable = null
|
||||
}
|
||||
if (cacheQueryDisposable != null && !cacheQueryDisposable!!.isDisposed) {
|
||||
cacheQueryDisposable!!.dispose()
|
||||
cacheQueryDisposable = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
dispose(null)
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
if (newText != "" && adapter?.hasNewFilter(newText) == true) {
|
||||
adapter?.setFilter(newText)
|
||||
fetchData()
|
||||
} else if (newText == "") {
|
||||
adapter?.setFilter("")
|
||||
adapter?.updateDataSet(contactItems as List<Nothing>?)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return onQueryTextChange(query)
|
||||
}
|
||||
|
||||
private fun checkAndHandleDoneMenuItem() {
|
||||
if (adapter != null && doneMenuItem != null) {
|
||||
doneMenuItem!!.isVisible =
|
||||
selectedCircleIds.size + selectedEmails.size + selectedGroupIds.size + selectedUserIds.size > 0 ||
|
||||
isPublicCall
|
||||
} else if (doneMenuItem != null) {
|
||||
doneMenuItem!!.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(openConversationEvent: OpenConversationEvent) {
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(openConversationEvent.bundle!!)
|
||||
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(chatIntent)
|
||||
}
|
||||
|
||||
override fun onItemClick(view: View, position: Int): Boolean {
|
||||
if (adapter?.getItem(position) is ContactItem) {
|
||||
if (!isNewConversationView && !isAddingParticipantsView) {
|
||||
createRoom(adapter?.getItem(position) as ContactItem)
|
||||
} else {
|
||||
updateSelection((adapter?.getItem(position) as ContactItem))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateSelection(contactItem: ContactItem) {
|
||||
contactItem.model.selected = !contactItem.model.selected
|
||||
updateSelectionLists(contactItem.model)
|
||||
if (CapabilitiesUtil.hasSpreedFeatureCapability(
|
||||
currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.LAST_ROOM_ACTIVITY
|
||||
) &&
|
||||
!CapabilitiesUtil.hasSpreedFeatureCapability(
|
||||
currentUser?.capabilities?.spreedCapability!!, SpreedFeatures.INVITE_GROUPS_AND_MAILS
|
||||
) &&
|
||||
isValidGroupSelection(contactItem, contactItem.model, adapter)
|
||||
) {
|
||||
val currentItems: List<ContactItem> = adapter?.currentItems as List<ContactItem>
|
||||
var internalParticipant: Participant
|
||||
for (i in currentItems.indices) {
|
||||
internalParticipant = currentItems[i].model
|
||||
if (internalParticipant.calculatedActorId == contactItem.model.calculatedActorId &&
|
||||
internalParticipant.calculatedActorType == Participant.ActorType.GROUPS &&
|
||||
internalParticipant.selected
|
||||
) {
|
||||
internalParticipant.selected = false
|
||||
selectedGroupIds.remove(internalParticipant.calculatedActorId!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter?.notifyDataSetChanged()
|
||||
checkAndHandleDoneMenuItem()
|
||||
}
|
||||
|
||||
private fun createRoom(contactItem: ContactItem) {
|
||||
var roomType = "1"
|
||||
if (Participant.ActorType.GROUPS == contactItem.model.actorType) {
|
||||
roomType = "2"
|
||||
}
|
||||
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser!!, intArrayOf(ApiUtils.API_V4, 1))
|
||||
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
|
||||
apiVersion,
|
||||
currentUser!!.baseUrl!!,
|
||||
roomType,
|
||||
null,
|
||||
contactItem.model.calculatedActorId,
|
||||
null
|
||||
)
|
||||
ncApi.createRoom(credentials, retrofitBucket.url, retrofitBucket.queryMap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(roomOverall: RoomOverall) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
|
||||
// bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
|
||||
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(bundle)
|
||||
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(chatIntent)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateSelectionLists(participant: Participant) {
|
||||
if (Participant.ActorType.GROUPS == participant.actorType) {
|
||||
if (participant.selected) {
|
||||
selectedGroupIds.add(participant.calculatedActorId!!)
|
||||
} else {
|
||||
selectedGroupIds.remove(participant.calculatedActorId!!)
|
||||
}
|
||||
} else if (Participant.ActorType.EMAILS == participant.actorType) {
|
||||
if (participant.selected) {
|
||||
selectedEmails.add(participant.calculatedActorId!!)
|
||||
} else {
|
||||
selectedEmails.remove(participant.calculatedActorId!!)
|
||||
}
|
||||
} else if (Participant.ActorType.CIRCLES == participant.actorType) {
|
||||
if (participant.selected) {
|
||||
selectedCircleIds.add(participant.calculatedActorId!!)
|
||||
} else {
|
||||
selectedCircleIds.remove(participant.calculatedActorId!!)
|
||||
}
|
||||
} else {
|
||||
if (participant.selected) {
|
||||
selectedUserIds.add(participant.calculatedActorId!!)
|
||||
} else {
|
||||
selectedUserIds.remove(participant.calculatedActorId!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidGroupSelection(
|
||||
contactItem: ContactItem,
|
||||
participant: Participant,
|
||||
adapter: FlexibleAdapter<*>?
|
||||
): Boolean {
|
||||
return Participant.ActorType.GROUPS == contactItem.model.actorType &&
|
||||
participant.selected && adapter?.selectedItemCount!! > 1
|
||||
}
|
||||
|
||||
private fun listOpenConversations() {
|
||||
val intent = Intent(this, ListOpenConversationsActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun toggleCallHeader() {
|
||||
toggleConversationPrivacyLayout(isPublicCall)
|
||||
isPublicCall = !isPublicCall
|
||||
enableContactForNonPublicCall()
|
||||
checkAndHandleDoneMenuItem()
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun enableContactForNonPublicCall() {
|
||||
for (i in 0 until adapter!!.itemCount) {
|
||||
if (adapter?.getItem(i) is ContactItem) {
|
||||
val contactItem: ContactItem = adapter?.getItem(i) as ContactItem
|
||||
if (Participant.ActorType.GROUPS == contactItem.model.actorType) {
|
||||
contactItem.isEnabled = !isPublicCall
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleConversationPrivacyLayout(showInitialLayout: Boolean) {
|
||||
if (showInitialLayout) {
|
||||
binding.publicConversationCreate.visibility = View.VISIBLE
|
||||
binding.publicConversationInfo.visibility = View.GONE
|
||||
} else {
|
||||
binding.publicConversationCreate.visibility = View.GONE
|
||||
binding.publicConversationInfo.visibility = View.VISIBLE
|
||||
binding.listOpenConversations.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ContactsActivity::class.simpleName
|
||||
const val RETRIES: Long = 3
|
||||
const val CONTACTS_BATCH_SIZE: Int = 50
|
||||
const val HEADER_ELEVATION: Int = 5
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
package com.nextcloud.talk.contacts
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -19,8 +18,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
|
||||
import com.nextcloud.talk.components.SetupSystemBars
|
||||
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
@ -36,7 +37,7 @@ class ContactsActivityCompose : BaseActivity() {
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
contactsViewModel = ViewModelProvider(this, viewModelFactory)[ContactsViewModel::class.java]
|
||||
setContent {
|
||||
val isAddParticipants = intent.getBooleanExtra("isAddParticipants", false)
|
||||
val isAddParticipants = intent.getBooleanExtra(BundleKeys.KEY_ADD_PARTICIPANTS, false)
|
||||
contactsViewModel.updateIsAddParticipants(isAddParticipants)
|
||||
if (isAddParticipants) {
|
||||
contactsViewModel.updateShareTypes(
|
||||
@ -50,14 +51,10 @@ class ContactsActivityCompose : BaseActivity() {
|
||||
}
|
||||
val colorScheme = viewThemeUtils.getColorScheme(this)
|
||||
val uiState = contactsViewModel.contactsViewState.collectAsStateWithLifecycle()
|
||||
|
||||
val selectedParticipants = remember {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableArrayListExtra("selectedParticipants", AutocompleteUser::class.java)
|
||||
?: emptyList()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
intent.getParcelableArrayListExtra("selectedParticipants") ?: emptyList()
|
||||
}
|
||||
intent?.getParcelableArrayListExtraProvider<AutocompleteUser>("selectedParticipants")
|
||||
?: emptyList()
|
||||
}.toSet().toMutableList()
|
||||
contactsViewModel.updateSelectedParticipants(selectedParticipants)
|
||||
|
||||
|
@ -15,7 +15,6 @@ import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
@ -26,8 +25,6 @@ import com.nextcloud.talk.contacts.components.ConversationCreationOptions
|
||||
|
||||
@Composable
|
||||
fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiState) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val searchQuery by contactsViewModel.searchQuery.collectAsStateWithLifecycle()
|
||||
val isSearchActive by contactsViewModel.isSearchActive.collectAsStateWithLifecycle()
|
||||
val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsStateWithLifecycle()
|
||||
@ -57,17 +54,17 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
|
||||
},
|
||||
content = {
|
||||
Column(
|
||||
Modifier.padding(it)
|
||||
Modifier
|
||||
.padding(it)
|
||||
.background(colorResource(id = R.color.bg_default))
|
||||
) {
|
||||
ConversationCreationOptions(
|
||||
context = context,
|
||||
contactsViewModel = contactsViewModel
|
||||
)
|
||||
if (!isAddParticipants) {
|
||||
ConversationCreationOptions()
|
||||
}
|
||||
|
||||
ContactsList(
|
||||
contactsUiState = uiState,
|
||||
contactsViewModel = contactsViewModel,
|
||||
context = context
|
||||
contactsViewModel = contactsViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
package com.nextcloud.talk.contacts.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@ -18,15 +17,18 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.nextcloud.talk.contacts.CompanionClass
|
||||
import com.nextcloud.talk.contacts.ContactsUiState
|
||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||
|
||||
@Composable
|
||||
fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsViewModel, context: Context) {
|
||||
fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsViewModel) {
|
||||
val context = LocalContext.current
|
||||
when (contactsUiState) {
|
||||
is ContactsUiState.None -> {
|
||||
}
|
||||
|
||||
is ContactsUiState.Loading -> {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@ -35,6 +37,7 @@ fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsVi
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
is ContactsUiState.Success -> {
|
||||
val contacts = contactsUiState.contacts
|
||||
Log.d(CompanionClass.TAG, "Contacts:$contacts")
|
||||
@ -42,6 +45,7 @@ fun ContactsList(contactsUiState: ContactsUiState, contactsViewModel: ContactsVi
|
||||
ContactsItem(contacts, contactsViewModel, context)
|
||||
}
|
||||
}
|
||||
|
||||
is ContactsUiState.Error -> {
|
||||
val errorMessage = contactsUiState.message
|
||||
Box(
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
package com.nextcloud.talk.contacts.components
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@ -23,75 +22,71 @@ import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||
import com.nextcloud.talk.conversationcreation.ConversationCreationActivity
|
||||
import com.nextcloud.talk.openconversations.ListOpenConversationsActivity
|
||||
|
||||
@Composable
|
||||
fun ConversationCreationOptions(context: Context, contactsViewModel: ContactsViewModel) {
|
||||
val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsState()
|
||||
if (!isAddParticipants) {
|
||||
Column {
|
||||
Row(
|
||||
fun ConversationCreationOptions() {
|
||||
val context = LocalContext.current
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
|
||||
.clickable {
|
||||
val intent = Intent(context, ConversationCreationActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.baseline_chat_bubble_outline_24),
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
|
||||
.clickable {
|
||||
val intent = Intent(context, ConversationCreationActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.baseline_chat_bubble_outline_24),
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(40.dp)
|
||||
.padding(8.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
text = stringResource(R.string.nc_create_new_conversation),
|
||||
maxLines = 1,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
Row(
|
||||
.width(40.dp)
|
||||
.height(40.dp)
|
||||
.padding(8.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
|
||||
.clickable {
|
||||
val intent = Intent(context, ListOpenConversationsActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.List,
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(40.dp)
|
||||
.padding(8.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
text = stringResource(R.string.nc_join_open_conversations),
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
text = stringResource(R.string.nc_create_new_conversation),
|
||||
maxLines = 1,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
|
||||
.clickable {
|
||||
val intent = Intent(context, ListOpenConversationsActivity::class.java)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.List,
|
||||
modifier = Modifier
|
||||
.width(40.dp)
|
||||
.height(40.dp)
|
||||
.padding(8.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
text = stringResource(R.string.nc_join_open_conversations),
|
||||
fontSize = 16.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.components.SetupSystemBars
|
||||
import com.nextcloud.talk.contacts.ContactsActivityCompose
|
||||
import com.nextcloud.talk.contacts.loadImage
|
||||
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
|
||||
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
|
||||
import com.nextcloud.talk.utils.CapabilitiesUtil
|
||||
import com.nextcloud.talk.utils.PickImage
|
||||
@ -163,7 +164,7 @@ fun ConversationCreationScreen(
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val data = result.data
|
||||
val selectedParticipants =
|
||||
data?.getParcelableArrayListExtra<AutocompleteUser>("selectedParticipants")
|
||||
data?.getParcelableArrayListExtraProvider<AutocompleteUser>("selectedParticipants")
|
||||
?: emptyList()
|
||||
val participants = selectedParticipants.toMutableList()
|
||||
conversationCreationViewModel.updateSelectedParticipants(participants)
|
||||
@ -383,7 +384,7 @@ fun AddParticipants(
|
||||
"selectedParticipants",
|
||||
participants as ArrayList<AutocompleteUser>
|
||||
)
|
||||
intent.putExtra("isAddParticipants", true)
|
||||
intent.putExtra(BundleKeys.KEY_ADD_PARTICIPANTS, true)
|
||||
intent.putExtra("isAddParticipantsEdit", true)
|
||||
launcher.launch(intent)
|
||||
},
|
||||
@ -417,7 +418,7 @@ fun AddParticipants(
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val intent = Intent(context, ContactsActivityCompose::class.java)
|
||||
intent.putExtra("isAddParticipants", true)
|
||||
intent.putExtra(BundleKeys.KEY_ADD_PARTICIPANTS, true)
|
||||
launcher.launch(intent)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@ -608,7 +609,8 @@ fun ShowChangePassword(onDismiss: () -> Unit, conversationCreationViewModel: Con
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
|
@ -11,6 +11,7 @@
|
||||
package com.nextcloud.talk.conversationinfo
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
@ -21,6 +22,8 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@ -49,21 +52,25 @@ import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
|
||||
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
|
||||
import com.nextcloud.talk.contacts.ContactsActivity
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.contacts.ContactsActivityCompose
|
||||
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
|
||||
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
|
||||
import com.nextcloud.talk.databinding.DialogBanParticipantBinding
|
||||
import com.nextcloud.talk.events.EventStatus
|
||||
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
|
||||
import com.nextcloud.talk.extensions.loadConversationAvatar
|
||||
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
|
||||
import com.nextcloud.talk.extensions.loadSystemAvatar
|
||||
import com.nextcloud.talk.extensions.loadUserAvatar
|
||||
import com.nextcloud.talk.jobs.AddParticipantsToConversation
|
||||
import com.nextcloud.talk.jobs.DeleteConversationWorker
|
||||
import com.nextcloud.talk.jobs.LeaveConversationWorker
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
|
||||
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
|
||||
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
|
||||
import com.nextcloud.talk.models.json.conversations.ConversationEnums
|
||||
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
|
||||
@ -126,12 +133,9 @@ class ConversationInfoActivity :
|
||||
private lateinit var conversationUser: User
|
||||
private var hasAvatarSpacing: Boolean = false
|
||||
private lateinit var credentials: String
|
||||
private var roomDisposable: Disposable? = null
|
||||
private var participantsDisposable: Disposable? = null
|
||||
|
||||
private var databaseStorageModule: DatabaseStorageModule? = null
|
||||
|
||||
// private var conversation: Conversation? = null
|
||||
private var conversation: ConversationModel? = null
|
||||
|
||||
private var adapter: FlexibleAdapter<ParticipantItem>? = null
|
||||
@ -151,6 +155,18 @@ class ConversationInfoActivity :
|
||||
return null
|
||||
}
|
||||
|
||||
private val addParticipantsResult = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
executeIfResultOk(it) { intent ->
|
||||
val selectedParticipants =
|
||||
intent?.getParcelableArrayListExtraProvider<AutocompleteUser>("selectedParticipants")
|
||||
?: emptyList()
|
||||
val participants = selectedParticipants.toMutableList()
|
||||
addParticipantsToConversation(participants)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
@ -190,7 +206,7 @@ class ConversationInfoActivity :
|
||||
binding.deleteConversationAction.setOnClickListener { showDeleteConversationDialog() }
|
||||
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
|
||||
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog() }
|
||||
binding.addParticipantsAction.setOnClickListener { addParticipants() }
|
||||
binding.addParticipantsAction.setOnClickListener { selectParticipantsToAdd() }
|
||||
binding.listBansButton.setOnClickListener { listBans() }
|
||||
|
||||
viewModel.getRoom(conversationUser, conversationToken)
|
||||
@ -237,9 +253,11 @@ class ConversationInfoActivity :
|
||||
when (state) {
|
||||
is ConversationInfoViewModel.SetConversationReadOnlyViewState.Success -> {
|
||||
}
|
||||
|
||||
is ConversationInfoViewModel.SetConversationReadOnlyViewState.Error -> {
|
||||
Snackbar.make(binding.root, R.string.conversation_read_only_failed, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
is ConversationInfoViewModel.SetConversationReadOnlyViewState.None -> {
|
||||
}
|
||||
}
|
||||
@ -249,6 +267,7 @@ class ConversationInfoActivity :
|
||||
when (uiState) {
|
||||
is ConversationInfoViewModel.ClearChatHistoryViewState.None -> {
|
||||
}
|
||||
|
||||
is ConversationInfoViewModel.ClearChatHistoryViewState.Success -> {
|
||||
Snackbar.make(
|
||||
binding.root,
|
||||
@ -256,6 +275,7 @@ class ConversationInfoActivity :
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
is ConversationInfoViewModel.ClearChatHistoryViewState.Error -> {
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
Log.e(TAG, "failed to clear chat history", uiState.exception)
|
||||
@ -652,7 +672,15 @@ class ConversationInfoActivity :
|
||||
.commit()
|
||||
}
|
||||
|
||||
private fun addParticipants() {
|
||||
private fun executeIfResultOk(result: ActivityResult, onResult: (intent: Intent?) -> Unit) {
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
onResult(result.data)
|
||||
} else {
|
||||
Log.e(ChatActivity.TAG, "resultCode for received intent was != ok")
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectParticipantsToAdd() {
|
||||
val bundle = Bundle()
|
||||
val existingParticipantsId = arrayListOf<String>()
|
||||
|
||||
@ -666,9 +694,61 @@ class ConversationInfoActivity :
|
||||
bundle.putStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS, existingParticipantsId)
|
||||
bundle.putString(BundleKeys.KEY_TOKEN, conversation!!.token)
|
||||
|
||||
val intent = Intent(this, ContactsActivity::class.java)
|
||||
val intent = Intent(this, ContactsActivityCompose::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
|
||||
addParticipantsResult.launch(intent)
|
||||
}
|
||||
|
||||
private fun addParticipantsToConversation(participants: List<AutocompleteUser>) {
|
||||
val groupIdsArray: MutableSet<String> = HashSet()
|
||||
val emailIdsArray: MutableSet<String> = HashSet()
|
||||
val circleIdsArray: MutableSet<String> = HashSet()
|
||||
val userIdsArray: MutableSet<String> = HashSet()
|
||||
|
||||
participants.forEach { participant ->
|
||||
when (participant.source) {
|
||||
Participant.ActorType.GROUPS.name.lowercase() -> groupIdsArray.add(participant.id!!)
|
||||
Participant.ActorType.EMAILS.name.lowercase() -> emailIdsArray.add(participant.id!!)
|
||||
Participant.ActorType.CIRCLES.name.lowercase() -> circleIdsArray.add(participant.id!!)
|
||||
else -> userIdsArray.add(participant.id!!)
|
||||
}
|
||||
}
|
||||
|
||||
val data = Data.Builder()
|
||||
data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!)
|
||||
data.putString(BundleKeys.KEY_TOKEN, conversationToken)
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_USERS, userIdsArray.toTypedArray())
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_GROUPS, groupIdsArray.toTypedArray())
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_EMAILS, emailIdsArray.toTypedArray())
|
||||
data.putStringArray(BundleKeys.KEY_SELECTED_CIRCLES, circleIdsArray.toTypedArray())
|
||||
val addParticipantsToConversationWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(
|
||||
AddParticipantsToConversation::class.java
|
||||
).setInputData(data.build()).build()
|
||||
WorkManager.getInstance().enqueue(addParticipantsToConversationWorker)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(addParticipantsToConversationWorker.id)
|
||||
.observeForever { workInfo: WorkInfo? ->
|
||||
if (workInfo != null) {
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.RUNNING -> {
|
||||
Log.d(TAG, "running AddParticipantsToConversation")
|
||||
}
|
||||
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
Log.d(TAG, "success AddParticipantsToConversation")
|
||||
getListOfParticipants()
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED -> {
|
||||
Log.d(TAG, "failed AddParticipantsToConversation")
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun leaveConversation() {
|
||||
@ -693,6 +773,7 @@ class ConversationInfoActivity :
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED -> {
|
||||
val errorType = workInfo.outputData.getString("error_type")
|
||||
if (errorType == LeaveConversationWorker.ERROR_NO_OTHER_MODERATORS_OR_OWNERS_LEFT) {
|
||||
@ -709,6 +790,7 @@ class ConversationInfoActivity :
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NEW_CONVERSATION
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SCROLL_TO_NOTIFICATION_CATEGORY
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SHARED_TEXT
|
||||
@ -1254,7 +1253,6 @@ class ConversationsListActivity :
|
||||
|
||||
private fun showNewConversationsScreen() {
|
||||
val intent = Intent(context, ContactsActivityCompose::class.java)
|
||||
intent.putExtra(KEY_NEW_CONVERSATION, true)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.extensions
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Parcelable
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inline fun <reified T : Parcelable> Intent.getParcelableExtraProvider(identifierParameter: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this.getParcelableExtra(identifierParameter, T::class.java)
|
||||
} else {
|
||||
this.getParcelableExtra(identifierParameter)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inline fun <reified T : Parcelable> Intent.getParcelableArrayListExtraProvider(
|
||||
identifierParameter: String
|
||||
): java.util.ArrayList<T>? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
this.getParcelableArrayListExtra(identifierParameter, T::class.java)
|
||||
} else {
|
||||
this.getParcelableArrayListExtra(identifierParameter)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user