|
|
|
@ -0,0 +1,964 @@
|
|
|
|
|
/*
|
|
|
|
|
* Nextcloud Talk application
|
|
|
|
|
*
|
|
|
|
|
* @author Mario Danic
|
|
|
|
|
* @author Marcel Hibbe
|
|
|
|
|
* @author Andy Scherzinger
|
|
|
|
|
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
|
|
|
|
* Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
|
|
|
|
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
package com.nextcloud.talk.controllers
|
|
|
|
|
|
|
|
|
|
import android.app.SearchManager
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.graphics.PorterDuff
|
|
|
|
|
import android.os.Build
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.text.InputType
|
|
|
|
|
import android.util.Log
|
|
|
|
|
import android.view.Menu
|
|
|
|
|
import android.view.MenuInflater
|
|
|
|
|
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.WorkManager
|
|
|
|
|
import autodagger.AutoInjector
|
|
|
|
|
import com.bluelinelabs.logansquare.LoganSquare
|
|
|
|
|
import com.nextcloud.talk.R
|
|
|
|
|
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.application.NextcloudTalkApplication.Companion.sharedApplication
|
|
|
|
|
import com.nextcloud.talk.controllers.base.NewBaseController
|
|
|
|
|
import com.nextcloud.talk.controllers.bottomsheet.ConversationOperationEnum
|
|
|
|
|
import com.nextcloud.talk.controllers.util.viewBinding
|
|
|
|
|
import com.nextcloud.talk.databinding.ControllerContactsRvBinding
|
|
|
|
|
import com.nextcloud.talk.events.OpenConversationEvent
|
|
|
|
|
import com.nextcloud.talk.jobs.AddParticipantsToConversation
|
|
|
|
|
import com.nextcloud.talk.models.RetrofitBucket
|
|
|
|
|
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
|
|
|
|
import com.nextcloud.talk.models.database.UserEntity
|
|
|
|
|
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
|
|
|
|
|
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
|
|
|
|
|
import com.nextcloud.talk.models.json.conversations.Conversation
|
|
|
|
|
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.ui.dialog.ContactsBottomDialog
|
|
|
|
|
import com.nextcloud.talk.utils.ApiUtils
|
|
|
|
|
import com.nextcloud.talk.utils.ConductorRemapping
|
|
|
|
|
import com.nextcloud.talk.utils.bundle.BundleKeys
|
|
|
|
|
import com.nextcloud.talk.utils.database.user.UserUtils
|
|
|
|
|
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.EventBus
|
|
|
|
|
import org.greenrobot.eventbus.Subscribe
|
|
|
|
|
import org.greenrobot.eventbus.ThreadMode
|
|
|
|
|
import org.parceler.Parcels
|
|
|
|
|
import java.io.IOException
|
|
|
|
|
import java.util.ArrayList
|
|
|
|
|
import java.util.Collections
|
|
|
|
|
import java.util.HashMap
|
|
|
|
|
import java.util.HashSet
|
|
|
|
|
import java.util.Locale
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@AutoInjector(NextcloudTalkApplication::class)
|
|
|
|
|
class ContactsController(args: Bundle) :
|
|
|
|
|
NewBaseController(R.layout.controller_contacts_rv),
|
|
|
|
|
SearchView.OnQueryTextListener,
|
|
|
|
|
FlexibleAdapter.OnItemClickListener {
|
|
|
|
|
private val binding: ControllerContactsRvBinding by viewBinding(ControllerContactsRvBinding::bind)
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var userUtils: UserUtils
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var eventBus: EventBus
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
lateinit var ncApi: NcApi
|
|
|
|
|
|
|
|
|
|
private var credentials: String? = null
|
|
|
|
|
private var currentUser: UserEntity? = 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
|
|
|
|
|
private var contactsBottomDialog: ContactsBottomDialog? = null
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
setHasOptionsMenu(true)
|
|
|
|
|
sharedApplication!!.componentApplication.inject(this)
|
|
|
|
|
|
|
|
|
|
if (args.containsKey(BundleKeys.KEY_NEW_CONVERSATION)) {
|
|
|
|
|
isNewConversationView = true
|
|
|
|
|
existingParticipants = ArrayList()
|
|
|
|
|
} else if (args.containsKey(BundleKeys.KEY_ADD_PARTICIPANTS)) {
|
|
|
|
|
isAddingParticipantsView = true
|
|
|
|
|
conversationToken = args.getString(BundleKeys.KEY_TOKEN)
|
|
|
|
|
existingParticipants = ArrayList()
|
|
|
|
|
if (args.containsKey(BundleKeys.KEY_EXISTING_PARTICIPANTS)) {
|
|
|
|
|
existingParticipants = args.getStringArrayList(BundleKeys.KEY_EXISTING_PARTICIPANTS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
selectedUserIds = HashSet()
|
|
|
|
|
selectedGroupIds = HashSet()
|
|
|
|
|
selectedEmails = HashSet()
|
|
|
|
|
selectedCircleIds = HashSet()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onAttach(view: View) {
|
|
|
|
|
super.onAttach(view)
|
|
|
|
|
eventBus.register(this)
|
|
|
|
|
if (isNewConversationView) {
|
|
|
|
|
toggleNewCallHeaderVisibility(!isPublicCall)
|
|
|
|
|
}
|
|
|
|
|
if (isAddingParticipantsView) {
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkRelativeLayout.visibility = View.GONE
|
|
|
|
|
binding.conversationPrivacyToggle.callHeaderLayout.visibility = View.GONE
|
|
|
|
|
} else {
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkRelativeLayout.setOnClickListener {
|
|
|
|
|
joinConversationViaLink()
|
|
|
|
|
}
|
|
|
|
|
binding.conversationPrivacyToggle.callHeaderLayout.setOnClickListener {
|
|
|
|
|
toggleCallHeader()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onViewBound(view: View) {
|
|
|
|
|
super.onViewBound(view)
|
|
|
|
|
currentUser = userUtils.currentUser
|
|
|
|
|
if (currentUser != null) {
|
|
|
|
|
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
|
|
|
|
|
}
|
|
|
|
|
if (adapter == null) {
|
|
|
|
|
contactItems = ArrayList<AbstractFlexibleItem<*>>()
|
|
|
|
|
adapter = FlexibleAdapter(contactItems, activity, false)
|
|
|
|
|
if (currentUser != null) {
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setupAdapter()
|
|
|
|
|
prepareViews()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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)
|
|
|
|
|
} else {
|
|
|
|
|
val bundle = Bundle()
|
|
|
|
|
val roomType: Conversation.ConversationType = if (isPublicCall) {
|
|
|
|
|
Conversation.ConversationType.ROOM_PUBLIC_CALL
|
|
|
|
|
} else {
|
|
|
|
|
Conversation.ConversationType.ROOM_GROUP_CALL
|
|
|
|
|
}
|
|
|
|
|
val userIdsArray = ArrayList(selectedUserIds)
|
|
|
|
|
val groupIdsArray = ArrayList(selectedGroupIds)
|
|
|
|
|
val emailsArray = ArrayList(selectedEmails)
|
|
|
|
|
val circleIdsArray = ArrayList(selectedCircleIds)
|
|
|
|
|
bundle.putParcelable(BundleKeys.KEY_CONVERSATION_TYPE, Parcels.wrap(roomType))
|
|
|
|
|
bundle.putStringArrayList(BundleKeys.KEY_INVITED_PARTICIPANTS, userIdsArray)
|
|
|
|
|
bundle.putStringArrayList(BundleKeys.KEY_INVITED_GROUP, groupIdsArray)
|
|
|
|
|
bundle.putStringArrayList(BundleKeys.KEY_INVITED_EMAIL, emailsArray)
|
|
|
|
|
bundle.putStringArrayList(BundleKeys.KEY_INVITED_CIRCLE, circleIdsArray)
|
|
|
|
|
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_INVITE_USERS)
|
|
|
|
|
prepareAndShowBottomSheetWithBundle(bundle)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
addParticipantsToConversation()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun createRoom(roomType: String, sourceType: String?, userId: String) {
|
|
|
|
|
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
|
|
|
|
|
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
|
|
|
|
|
apiVersion,
|
|
|
|
|
currentUser!!.baseUrl,
|
|
|
|
|
roomType,
|
|
|
|
|
sourceType,
|
|
|
|
|
userId,
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
ncApi.createRoom(
|
|
|
|
|
credentials,
|
|
|
|
|
retrofitBucket.getUrl(), retrofitBucket.getQueryMap()
|
|
|
|
|
)
|
|
|
|
|
.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.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
|
|
|
|
|
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.getOcs().getData().getToken())
|
|
|
|
|
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.getOcs().getData().getRoomId())
|
|
|
|
|
|
|
|
|
|
// FIXME once APIv2 or later is used only, the createRoom already returns all the data
|
|
|
|
|
ncApi.getRoom(
|
|
|
|
|
credentials,
|
|
|
|
|
ApiUtils.getUrlForRoom(
|
|
|
|
|
apiVersion, currentUser!!.baseUrl,
|
|
|
|
|
roomOverall.getOcs().getData().getToken()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
|
.subscribe(object : Observer<RoomOverall> {
|
|
|
|
|
override fun onSubscribe(d: Disposable) {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
override fun onNext(roomOverall: RoomOverall) {
|
|
|
|
|
bundle.putParcelable(
|
|
|
|
|
BundleKeys.KEY_ACTIVE_CONVERSATION,
|
|
|
|
|
Parcels.wrap(roomOverall.getOcs().getData())
|
|
|
|
|
)
|
|
|
|
|
ConductorRemapping.remapChatController(
|
|
|
|
|
router, currentUser!!.id,
|
|
|
|
|
roomOverall.getOcs().getData().getToken(), bundle, true
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onError(e: Throwable) {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onComplete() {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onError(e: Throwable) {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onComplete() {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun addParticipantsToConversation() {
|
|
|
|
|
val userIdsArray: Array<String> = selectedUserIds.toTypedArray<String>()
|
|
|
|
|
val groupIdsArray: Array<String> = selectedGroupIds.toTypedArray<String>()
|
|
|
|
|
val emailsArray: Array<String> = selectedEmails.toTypedArray<String>()
|
|
|
|
|
val circleIdsArray: Array<String> = selectedCircleIds.toTypedArray<String>()
|
|
|
|
|
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)
|
|
|
|
|
router.popCurrentController()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun initSearchView() {
|
|
|
|
|
if (activity != null) {
|
|
|
|
|
val searchManager: SearchManager? = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager?
|
|
|
|
|
if (searchItem != null) {
|
|
|
|
|
searchView = MenuItemCompat.getActionView(searchItem) as 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
|
|
|
|
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(activity?.componentName))
|
|
|
|
|
}
|
|
|
|
|
searchView!!.setOnQueryTextListener(this)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
|
|
|
return when (item.itemId) {
|
|
|
|
|
R.id.home -> {
|
|
|
|
|
router.popCurrentController()
|
|
|
|
|
}
|
|
|
|
|
R.id.contacts_selection_done -> {
|
|
|
|
|
selectionDone()
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
super.onOptionsItemSelected(item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
|
|
|
super.onCreateOptionsMenu(menu, inflater)
|
|
|
|
|
inflater.inflate(R.menu.menu_contacts, menu)
|
|
|
|
|
searchItem = menu.findItem(R.id.action_search)
|
|
|
|
|
doneMenuItem = menu.findItem(R.id.contacts_selection_done)
|
|
|
|
|
initSearchView()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
|
|
|
super.onPrepareOptionsMenu(menu)
|
|
|
|
|
checkAndHandleDoneMenuItem()
|
|
|
|
|
if (adapter?.hasFilter() == true) {
|
|
|
|
|
searchItem!!.expandActionView()
|
|
|
|
|
searchView!!.setQuery(adapter!!.getFilter(String::class.java) as CharSequence, false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("Detekt.TooGenericExceptionCaught")
|
|
|
|
|
private fun fetchData() {
|
|
|
|
|
dispose(null)
|
|
|
|
|
alreadyFetching = true
|
|
|
|
|
userHeaderItems = HashMap<String, GenericTextHeaderItem>()
|
|
|
|
|
val query = adapter!!.getFilter(String::class.java) as String?
|
|
|
|
|
val retrofitBucket: RetrofitBucket =
|
|
|
|
|
ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl, query)
|
|
|
|
|
val modifiedQueryMap: HashMap<String, Any?> = HashMap<String, Any?>(retrofitBucket.getQueryMap())
|
|
|
|
|
modifiedQueryMap.put("limit", CONTACTS_BATCH_SIZE)
|
|
|
|
|
if (isAddingParticipantsView) {
|
|
|
|
|
modifiedQueryMap.put("itemId", conversationToken)
|
|
|
|
|
}
|
|
|
|
|
val shareTypesList: ArrayList<String> = ArrayList()
|
|
|
|
|
// users
|
|
|
|
|
shareTypesList.add("0")
|
|
|
|
|
if (!isAddingParticipantsView) {
|
|
|
|
|
// groups
|
|
|
|
|
shareTypesList.add("1")
|
|
|
|
|
} else if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "invite-groups-and-mails")) {
|
|
|
|
|
// groups
|
|
|
|
|
shareTypesList.add("1")
|
|
|
|
|
// emails
|
|
|
|
|
shareTypesList.add("4")
|
|
|
|
|
}
|
|
|
|
|
if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "circles-support")) {
|
|
|
|
|
// circles
|
|
|
|
|
shareTypesList.add("7")
|
|
|
|
|
}
|
|
|
|
|
modifiedQueryMap.put("shareTypes[]", shareTypesList)
|
|
|
|
|
ncApi.getContactsWithSearchParam(
|
|
|
|
|
credentials,
|
|
|
|
|
retrofitBucket.getUrl(), 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) {
|
|
|
|
|
val newUserItemList = processAutocompleteUserList(responseBody)
|
|
|
|
|
|
|
|
|
|
userHeaderItems = HashMap<String, GenericTextHeaderItem>()
|
|
|
|
|
contactItems!!.addAll(newUserItemList)
|
|
|
|
|
|
|
|
|
|
sortUserItems(newUserItemList)
|
|
|
|
|
|
|
|
|
|
if (newUserItemList.size > 0) {
|
|
|
|
|
adapter?.updateDataSet(newUserItemList as List<Nothing>?)
|
|
|
|
|
} else {
|
|
|
|
|
adapter?.filterItems()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
|
|
|
|
} catch (npe: NullPointerException) {
|
|
|
|
|
// view binding can be null
|
|
|
|
|
// since this is called asynchronously and UI might have been destroyed in the meantime
|
|
|
|
|
Log.i(TAG, "UI destroyed - view binding already gone")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onError(e: Throwable) {
|
|
|
|
|
try {
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
|
|
|
|
} catch (npe: NullPointerException) {
|
|
|
|
|
// view binding can be null
|
|
|
|
|
// since this is called asynchronously and UI might have been destroyed in the meantime
|
|
|
|
|
Log.i(TAG, "UI destroyed - view binding already gone")
|
|
|
|
|
}
|
|
|
|
|
dispose(contactsQueryDisposable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onComplete() {
|
|
|
|
|
try {
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
|
|
|
|
} catch (npe: NullPointerException) {
|
|
|
|
|
// view binding can be null
|
|
|
|
|
// since this is called asynchronously and UI might have been destroyed in the meantime
|
|
|
|
|
Log.i(TAG, "UI destroyed - view binding already gone")
|
|
|
|
|
}
|
|
|
|
|
dispose(contactsQueryDisposable)
|
|
|
|
|
alreadyFetching = false
|
|
|
|
|
disengageProgressBar()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun processAutocompleteUserList(responseBody: ResponseBody): MutableList<AbstractFlexibleItem<*>> {
|
|
|
|
|
try {
|
|
|
|
|
val autocompleteOverall: AutocompleteOverall = LoganSquare.parse<AutocompleteOverall>(
|
|
|
|
|
responseBody.string(),
|
|
|
|
|
AutocompleteOverall::class.java
|
|
|
|
|
)
|
|
|
|
|
val autocompleteUsersList: ArrayList<AutocompleteUser> = ArrayList<AutocompleteUser>()
|
|
|
|
|
autocompleteUsersList.addAll(autocompleteOverall.ocs!!.data!!)
|
|
|
|
|
return processAutocompleteUserList(autocompleteUsersList)
|
|
|
|
|
} catch (ioe: IOException) {
|
|
|
|
|
Log.e(TAG, "Parsing response body failed while getting contacts", ioe)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ArrayList<AbstractFlexibleItem<*>>()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun processAutocompleteUserList(
|
|
|
|
|
autocompleteUsersList: ArrayList<AutocompleteUser>
|
|
|
|
|
): MutableList<AbstractFlexibleItem<*>> {
|
|
|
|
|
var participant: Participant
|
|
|
|
|
val actorTypeConverter = EnumActorTypeConverter()
|
|
|
|
|
val newUserItemList: MutableList<AbstractFlexibleItem<*>> = ArrayList<AbstractFlexibleItem<*>>()
|
|
|
|
|
for (autocompleteUser in autocompleteUsersList) {
|
|
|
|
|
if (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)
|
|
|
|
|
userHeaderItems.put(headerTitle, genericTextHeaderItem)
|
|
|
|
|
}
|
|
|
|
|
val newContactItem = ContactItem(
|
|
|
|
|
participant,
|
|
|
|
|
currentUser,
|
|
|
|
|
userHeaderItems[headerTitle]
|
|
|
|
|
)
|
|
|
|
|
if (!contactItems!!.contains(newContactItem)) {
|
|
|
|
|
newUserItemList.add(newContactItem)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return newUserItemList
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getHeaderTitle(participant: Participant): String {
|
|
|
|
|
return when {
|
|
|
|
|
participant.getActorType() == Participant.ActorType.GROUPS -> {
|
|
|
|
|
resources!!.getString(R.string.nc_groups)
|
|
|
|
|
}
|
|
|
|
|
participant.getActorType() == Participant.ActorType.CIRCLES -> {
|
|
|
|
|
resources!!.getString(R.string.nc_circles)
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
participant.getDisplayName().substring(0, 1).toUpperCase(Locale.getDefault())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun createParticipant(
|
|
|
|
|
autocompleteUser: AutocompleteUser,
|
|
|
|
|
actorTypeConverter: EnumActorTypeConverter
|
|
|
|
|
): Participant {
|
|
|
|
|
val participant = Participant()
|
|
|
|
|
participant.setActorId(autocompleteUser.id)
|
|
|
|
|
participant.setActorType(actorTypeConverter.getFromString(autocompleteUser.source))
|
|
|
|
|
participant.setDisplayName(autocompleteUser.label)
|
|
|
|
|
participant.setSource(autocompleteUser.source)
|
|
|
|
|
|
|
|
|
|
return participant
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun sortUserItems(newUserItemList: MutableList<AbstractFlexibleItem<*>>) {
|
|
|
|
|
Collections.sort(
|
|
|
|
|
newUserItemList,
|
|
|
|
|
{ o1: AbstractFlexibleItem<*>, o2: AbstractFlexibleItem<*> ->
|
|
|
|
|
val firstName: String = if (o1 is ContactItem) {
|
|
|
|
|
(o1 as ContactItem).model.getDisplayName()
|
|
|
|
|
} else {
|
|
|
|
|
(o1 as GenericTextHeaderItem).model
|
|
|
|
|
}
|
|
|
|
|
val secondName: String = if (o2 is ContactItem) {
|
|
|
|
|
(o2 as ContactItem).model.getDisplayName()
|
|
|
|
|
} else {
|
|
|
|
|
(o2 as GenericTextHeaderItem).model
|
|
|
|
|
}
|
|
|
|
|
if (o1 is ContactItem && o2 is ContactItem) {
|
|
|
|
|
val firstSource: String = (o1 as ContactItem).model.getSource()
|
|
|
|
|
val secondSource: String = (o2 as ContactItem).model.getSource()
|
|
|
|
|
if (firstSource == secondSource) {
|
|
|
|
|
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First users
|
|
|
|
|
if ("users" == firstSource) {
|
|
|
|
|
return@sort -1
|
|
|
|
|
} else if ("users" == secondSource) {
|
|
|
|
|
return@sort 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then groups
|
|
|
|
|
if ("groups" == firstSource) {
|
|
|
|
|
return@sort -1
|
|
|
|
|
} else if ("groups" == secondSource) {
|
|
|
|
|
return@sort 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then circles
|
|
|
|
|
if ("circles" == firstSource) {
|
|
|
|
|
return@sort -1
|
|
|
|
|
} else if ("circles" == secondSource) {
|
|
|
|
|
return@sort 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise fall back to name sorting
|
|
|
|
|
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
|
|
|
|
}
|
|
|
|
|
firstName.compareTo(secondName, ignoreCase = true)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Collections.sort(
|
|
|
|
|
contactItems
|
|
|
|
|
) { o1: AbstractFlexibleItem<*>, o2: AbstractFlexibleItem<*> ->
|
|
|
|
|
val firstName: String = if (o1 is ContactItem) {
|
|
|
|
|
(o1 as ContactItem).model.getDisplayName()
|
|
|
|
|
} else {
|
|
|
|
|
(o1 as GenericTextHeaderItem).model
|
|
|
|
|
}
|
|
|
|
|
val secondName: String = if (o2 is ContactItem) {
|
|
|
|
|
(o2 as ContactItem).model.getDisplayName()
|
|
|
|
|
} else {
|
|
|
|
|
(o2 as GenericTextHeaderItem).model
|
|
|
|
|
}
|
|
|
|
|
if (o1 is ContactItem && o2 is ContactItem) {
|
|
|
|
|
if ("groups" == (o1 as ContactItem).model.getSource() &&
|
|
|
|
|
"groups" == (o2 as ContactItem).model.getSource()
|
|
|
|
|
) {
|
|
|
|
|
return@sort firstName.compareTo(secondName, ignoreCase = true)
|
|
|
|
|
} else if ("groups" == (o1 as ContactItem).model.getSource()) {
|
|
|
|
|
return@sort -1
|
|
|
|
|
} else if ("groups" == (o2 as ContactItem).model.getSource()) {
|
|
|
|
|
return@sort 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
firstName.compareTo(secondName, ignoreCase = true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun prepareViews() {
|
|
|
|
|
layoutManager = SmoothScrollLinearLayoutManager(activity)
|
|
|
|
|
binding.controllerGenericRv.recyclerView.layoutManager = layoutManager
|
|
|
|
|
binding.controllerGenericRv.recyclerView.setHasFixedSize(true)
|
|
|
|
|
binding.controllerGenericRv.recyclerView.adapter = adapter
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.setOnRefreshListener { fetchData() }
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary)
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout
|
|
|
|
|
.setProgressBackgroundColorSchemeResource(R.color.refresh_spinner_background)
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkImageView
|
|
|
|
|
.background
|
|
|
|
|
.setColorFilter(
|
|
|
|
|
ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null),
|
|
|
|
|
PorterDuff.Mode.SRC_IN
|
|
|
|
|
)
|
|
|
|
|
binding.conversationPrivacyToggle.publicCallLink
|
|
|
|
|
.background
|
|
|
|
|
.setColorFilter(
|
|
|
|
|
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null),
|
|
|
|
|
PorterDuff.Mode.SRC_IN
|
|
|
|
|
)
|
|
|
|
|
disengageProgressBar()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun disengageProgressBar() {
|
|
|
|
|
if (!alreadyFetching) {
|
|
|
|
|
binding.loadingContent.visibility = View.GONE
|
|
|
|
|
binding.controllerGenericRv.root.visibility = View.VISIBLE
|
|
|
|
|
if (isNewConversationView) {
|
|
|
|
|
binding.conversationPrivacyToggle.callHeaderLayout.visibility = View.VISIBLE
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkRelativeLayout.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 onSaveViewState(view: View, outState: Bundle) {
|
|
|
|
|
adapter?.onSaveInstanceState(outState)
|
|
|
|
|
super.onSaveViewState(view, outState)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
|
|
|
|
super.onRestoreViewState(view, savedViewState)
|
|
|
|
|
if (adapter != null) {
|
|
|
|
|
adapter?.onRestoreInstanceState(savedViewState)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
|
super.onDestroy()
|
|
|
|
|
dispose(null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("Detekt.TooGenericExceptionCaught")
|
|
|
|
|
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>?)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
binding.controllerGenericRv.swipeRefreshLayout.isEnabled = !adapter!!.hasFilter()
|
|
|
|
|
} catch (npe: NullPointerException) {
|
|
|
|
|
// view binding can be null
|
|
|
|
|
// since this is called asynchronously and UI might have been destroyed in the meantime
|
|
|
|
|
Log.i(TAG, "UI destroyed - view binding already gone")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override val title: String
|
|
|
|
|
get() = 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun prepareAndShowBottomSheetWithBundle(bundle: Bundle) {
|
|
|
|
|
// 11: create conversation-enter name for new conversation
|
|
|
|
|
// 10: get&join room when enter link
|
|
|
|
|
contactsBottomDialog = ContactsBottomDialog(activity!!, bundle)
|
|
|
|
|
contactsBottomDialog?.show()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
|
|
|
fun onMessageEvent(openConversationEvent: OpenConversationEvent) {
|
|
|
|
|
ConductorRemapping.remapChatController(
|
|
|
|
|
router, currentUser!!.id,
|
|
|
|
|
openConversationEvent.conversation!!.getToken(),
|
|
|
|
|
openConversationEvent.bundle!!, true
|
|
|
|
|
)
|
|
|
|
|
contactsBottomDialog?.dismiss()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onDetach(view: View) {
|
|
|
|
|
super.onDetach(view)
|
|
|
|
|
eventBus.unregister(this)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onItemClick(view: View, position: Int): Boolean {
|
|
|
|
|
if (adapter?.getItem(position) is ContactItem) {
|
|
|
|
|
if (!isNewConversationView && !isAddingParticipantsView) {
|
|
|
|
|
createRoom(adapter?.getItem(position) as ContactItem)
|
|
|
|
|
} else {
|
|
|
|
|
val participant: Participant = (adapter?.getItem(position) as ContactItem).model
|
|
|
|
|
updateSelection((adapter?.getItem(position) as ContactItem))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun updateSelection(contactItem: ContactItem) {
|
|
|
|
|
contactItem.model.isSelected = !contactItem.model.isSelected
|
|
|
|
|
updateSelectionLists(contactItem.model)
|
|
|
|
|
if (CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "last-room-activity") &&
|
|
|
|
|
!CapabilitiesUtil.hasSpreedFeatureCapability(currentUser, "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.getActorId() == contactItem.model.getActorId() &&
|
|
|
|
|
internalParticipant.getActorType() == Participant.ActorType.GROUPS &&
|
|
|
|
|
internalParticipant.isSelected
|
|
|
|
|
) {
|
|
|
|
|
internalParticipant.isSelected = false
|
|
|
|
|
selectedGroupIds.remove(internalParticipant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
adapter?.notifyDataSetChanged()
|
|
|
|
|
checkAndHandleDoneMenuItem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun createRoom(contactItem: ContactItem) {
|
|
|
|
|
var roomType = "1"
|
|
|
|
|
if ("groups" == contactItem.model.getSource()) {
|
|
|
|
|
roomType = "2"
|
|
|
|
|
}
|
|
|
|
|
val apiVersion: Int = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, 1))
|
|
|
|
|
val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
|
|
|
|
|
apiVersion,
|
|
|
|
|
currentUser!!.baseUrl,
|
|
|
|
|
roomType,
|
|
|
|
|
null,
|
|
|
|
|
contactItem.model.getActorId(),
|
|
|
|
|
null
|
|
|
|
|
)
|
|
|
|
|
ncApi.createRoom(
|
|
|
|
|
credentials,
|
|
|
|
|
retrofitBucket.getUrl(), retrofitBucket.getQueryMap()
|
|
|
|
|
)
|
|
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
|
.subscribe(object : Observer<RoomOverall> {
|
|
|
|
|
override fun onSubscribe(d: Disposable) {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
override fun onNext(roomOverall: RoomOverall) {
|
|
|
|
|
if (activity != null) {
|
|
|
|
|
val bundle = Bundle()
|
|
|
|
|
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser)
|
|
|
|
|
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.getOcs().getData().getToken())
|
|
|
|
|
bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.getOcs().getData().getRoomId())
|
|
|
|
|
bundle.putParcelable(
|
|
|
|
|
BundleKeys.KEY_ACTIVE_CONVERSATION,
|
|
|
|
|
Parcels.wrap(roomOverall.getOcs().getData())
|
|
|
|
|
)
|
|
|
|
|
ConductorRemapping.remapChatController(
|
|
|
|
|
router,
|
|
|
|
|
currentUser!!.id,
|
|
|
|
|
roomOverall.getOcs().getData().getToken(),
|
|
|
|
|
bundle,
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onError(e: Throwable) {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
override fun onComplete() {
|
|
|
|
|
// unused atm
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun updateSelectionLists(participant: Participant) {
|
|
|
|
|
if ("groups" == participant.getSource()) {
|
|
|
|
|
if (participant.isSelected) {
|
|
|
|
|
selectedGroupIds.add(participant.getActorId())
|
|
|
|
|
} else {
|
|
|
|
|
selectedGroupIds.remove(participant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
} else if ("emails" == participant.getSource()) {
|
|
|
|
|
if (participant.isSelected) {
|
|
|
|
|
selectedEmails.add(participant.getActorId())
|
|
|
|
|
} else {
|
|
|
|
|
selectedEmails.remove(participant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
} else if ("circles" == participant.getSource()) {
|
|
|
|
|
if (participant.isSelected) {
|
|
|
|
|
selectedCircleIds.add(participant.getActorId())
|
|
|
|
|
} else {
|
|
|
|
|
selectedCircleIds.remove(participant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (participant.isSelected) {
|
|
|
|
|
selectedUserIds.add(participant.getActorId())
|
|
|
|
|
} else {
|
|
|
|
|
selectedUserIds.remove(participant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun isValidGroupSelection(
|
|
|
|
|
contactItem: ContactItem,
|
|
|
|
|
participant: Participant,
|
|
|
|
|
adapter: FlexibleAdapter<*>?
|
|
|
|
|
): Boolean {
|
|
|
|
|
return "groups" == contactItem.model.getSource() && participant.isSelected && adapter?.selectedItemCount!! > 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun joinConversationViaLink() {
|
|
|
|
|
val bundle = Bundle()
|
|
|
|
|
bundle.putSerializable(BundleKeys.KEY_OPERATION_CODE, ConversationOperationEnum.OPS_CODE_GET_AND_JOIN_ROOM)
|
|
|
|
|
prepareAndShowBottomSheetWithBundle(bundle)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun toggleCallHeader() {
|
|
|
|
|
toggleNewCallHeaderVisibility(isPublicCall)
|
|
|
|
|
isPublicCall = !isPublicCall
|
|
|
|
|
|
|
|
|
|
if (isPublicCall) {
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkRelativeLayout.visibility = View.GONE
|
|
|
|
|
updateGroupParticipantSelection()
|
|
|
|
|
} else {
|
|
|
|
|
binding.joinConversationViaLink.joinConversationViaLinkRelativeLayout.visibility = View.VISIBLE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enableContactForNonPublicCall()
|
|
|
|
|
checkAndHandleDoneMenuItem()
|
|
|
|
|
adapter?.notifyDataSetChanged()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun updateGroupParticipantSelection() {
|
|
|
|
|
val currentItems: List<AbstractFlexibleItem<*>> = adapter?.currentItems as
|
|
|
|
|
List<AbstractFlexibleItem<*>>
|
|
|
|
|
var internalParticipant: Participant
|
|
|
|
|
for (i in currentItems.indices) {
|
|
|
|
|
if (currentItems[i] is ContactItem) {
|
|
|
|
|
internalParticipant = (currentItems[i] as ContactItem).model
|
|
|
|
|
if (internalParticipant.getActorType() == Participant.ActorType.GROUPS &&
|
|
|
|
|
internalParticipant.isSelected
|
|
|
|
|
) {
|
|
|
|
|
internalParticipant.isSelected = false
|
|
|
|
|
selectedGroupIds.remove(internalParticipant.getActorId())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ("groups" == contactItem.model.getSource()) {
|
|
|
|
|
contactItem.isEnabled = !isPublicCall
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("Detekt.TooGenericExceptionCaught")
|
|
|
|
|
private fun toggleNewCallHeaderVisibility(showInitialLayout: Boolean) {
|
|
|
|
|
try {
|
|
|
|
|
if (showInitialLayout) {
|
|
|
|
|
binding.conversationPrivacyToggle.initialRelativeLayout.visibility = View.VISIBLE
|
|
|
|
|
binding.conversationPrivacyToggle.secondaryRelativeLayout.visibility = View.GONE
|
|
|
|
|
} else {
|
|
|
|
|
binding.conversationPrivacyToggle.initialRelativeLayout.visibility = View.GONE
|
|
|
|
|
binding.conversationPrivacyToggle.secondaryRelativeLayout.visibility = View.VISIBLE
|
|
|
|
|
}
|
|
|
|
|
} catch (npe: NullPointerException) {
|
|
|
|
|
// view binding can be null
|
|
|
|
|
// since this is called asynchronously and UI might have been destroyed in the meantime
|
|
|
|
|
Log.i(TAG, "UI destroyed - view binding already gone")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
const val TAG = "ContactsController"
|
|
|
|
|
const val RETRIES: Long = 3
|
|
|
|
|
const val CONTACTS_BATCH_SIZE: Int = 50
|
|
|
|
|
const val HEADER_ELEVATION: Int = 5
|
|
|
|
|
}
|
|
|
|
|
}
|