/* * Nextcloud Talk application * * @author Mario Danic * Copyright (C) 2017 Mario Danic (mario@lovelyhq.com) * * 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 . */ package com.nextcloud.talk.controllers import android.app.SearchManager import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import android.os.Handler import android.text.InputType import android.text.TextUtils import android.util.Log import android.view.* import android.view.inputmethod.EditorInfo import android.widget.ProgressBar import android.widget.RelativeLayout import androidx.appcompat.widget.SearchView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.MenuItemCompat import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.work.Data import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import butterknife.BindView import butterknife.OnClick import butterknife.Optional import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler import com.bluelinelabs.logansquare.LoganSquare import com.kennyc.bottomsheet.BottomSheet import com.nextcloud.talk.R import com.nextcloud.talk.activities.MagicCallActivity import com.nextcloud.talk.adapters.items.GenericTextHeaderItem import com.nextcloud.talk.adapters.items.ProgressItem import com.nextcloud.talk.adapters.items.UserItem import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.controllers.base.BaseController import com.nextcloud.talk.controllers.bottomsheet.EntryMenuController import com.nextcloud.talk.controllers.bottomsheet.OperationsMenuController import com.nextcloud.talk.events.BottomSheetLockEvent 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.Conversation import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.sharees.Sharee import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConductorRemapping import com.nextcloud.talk.utils.KeyboardUtils import com.nextcloud.talk.utils.bundle.BundleKeys import com.uber.autodispose.AutoDispose import eu.davidea.fastscroller.FastScroller 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.ResponseBody import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.koin.android.ext.android.inject import org.parceler.Parcels import java.util.ArrayList import java.util.HashMap import java.util.HashSet import kotlin.Comparator import kotlin.String class ContactsController : BaseController, SearchView.OnQueryTextListener, FlexibleAdapter.OnItemClickListener, FastScroller.OnScrollStateChangeListener{ val usersRepository: UsersRepository by inject() val ncApi: NcApi by inject() @JvmField @BindView(R.id.initial_relative_layout) var initialRelativeLayout: RelativeLayout? = null @JvmField @BindView(R.id.secondary_relative_layout) var secondaryRelativeLayout: RelativeLayout? = null @JvmField @BindView(R.id.progressBar) var progressBar: ProgressBar? = null @JvmField @BindView(R.id.recyclerView) var recyclerView: RecyclerView? = null @JvmField @BindView(R.id.swipe_refresh_layout) var swipeRefreshLayout: SwipeRefreshLayout? = null @JvmField @BindView(R.id.fast_scroller) var fastScroller: FastScroller? = null @JvmField @BindView(R.id.call_header_layout) var conversationPrivacyToogleLayout: RelativeLayout? = null @JvmField @BindView(R.id.joinConversationViaLinkRelativeLayout) var joinConversationViaLinkLayout: RelativeLayout? = null @JvmField @BindView(R.id.generic_rv_layout) var genericRvLayout: CoordinatorLayout? = null private var credentials: String? = null private var currentUser: UserNgEntity? = null private var adapter: FlexibleAdapter>? = null private var contactItems: MutableList>? = null private var bottomSheet: BottomSheet? = null private var bottomSheetView: View? = null private var layoutManager: SmoothScrollLinearLayoutManager? = null private var searchItem: MenuItem? = null private var searchView: SearchView? = null private var isNewConversationView: Boolean = false private var isPublicCall: Boolean = false private var userHeaderItems = HashMap() private var alreadyFetching = false private var doneMenuItem: MenuItem? = null private val selectedUserIds: MutableSet = mutableSetOf() private val selectedGroupIds: MutableSet = mutableSetOf() private var existingParticipants: List? = null private var isAddingParticipantsView: Boolean = false private var conversationToken: String? = null constructor() : super() { setHasOptionsMenu(true) } constructor(args: Bundle) : super() { setHasOptionsMenu(true) 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) } } } override fun inflateView( inflater: LayoutInflater, container: ViewGroup ): View { return inflater.inflate(R.layout.controller_contacts_rv, container, false) } override fun onDetach(view: View) { eventBus.unregister(this) super.onDetach(view) } override fun onAttach(view: View) { super.onAttach(view) eventBus.register(this) if (isNewConversationView) { toggleNewCallHeaderVisibility(!isPublicCall) } if (isAddingParticipantsView) { joinConversationViaLinkLayout!!.visibility = View.GONE conversationPrivacyToogleLayout!!.visibility = View.GONE } } override fun onViewBound(view: View) { super.onViewBound(view) GlobalScope.launch { currentUser = usersRepository.getActiveUser() if (currentUser != null) { credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token) } if (adapter == null) { contactItems = ArrayList() adapter = FlexibleAdapter(contactItems, activity, true) if (currentUser != null) { fetchData(true) } } setupAdapter() withContext(Dispatchers.Main) { prepareViews() } } } private fun setupAdapter() { adapter!!.setNotifyChangeOfUnfilteredItems(true) .mode = SelectableAdapter.Mode.MULTI adapter!!.addListener(this) } private fun selectionDone() { if (!isAddingParticipantsView) { if (!isPublicCall && selectedGroupIds.size + selectedUserIds.size == 1) { val userId: String var roomType = "1" if (selectedGroupIds.size == 1) { roomType = "2" userId = selectedGroupIds.iterator() .next() } else { userId = selectedUserIds.iterator() .next() } val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( currentUser!!.baseUrl, roomType, userId, null ) ncApi.createRoom( credentials, retrofitBucket.url, retrofitBucket.queryMap ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(scopeProvider)) .subscribe(object : Observer { override fun onSubscribe(d: Disposable) { } override fun onNext(roomOverall: RoomOverall) { val conversationIntent = Intent(activity, MagicCallActivity::class.java) val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser) bundle.putString( BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs.data.token ) bundle.putString( BundleKeys.KEY_ROOM_ID, roomOverall.ocs.data.conversationId ) if (currentUser!!.hasSpreedFeatureCapability("chat-v2")) { ncApi.getRoom( credentials, ApiUtils.getRoom( currentUser!!.baseUrl, roomOverall.ocs.data.token ) ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(scopeProvider)) .subscribe(object : Observer { override fun onSubscribe(d: Disposable) { } override fun onNext(roomOverall: RoomOverall) { bundle.putParcelable( BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(roomOverall.ocs.data) ) ConductorRemapping.remapChatController( router, currentUser!!.id!!, roomOverall.ocs.data.token!!, bundle, true ) } override fun onError(e: Throwable) { } override fun onComplete() { } }) } else { conversationIntent.putExtras(bundle) startActivity(conversationIntent) Handler().postDelayed({ if (!isDestroyed && !isBeingDestroyed) { router.popCurrentController() } }, 100) } } override fun onError(e: Throwable) { } override fun onComplete() {} }) } else { val bundle = Bundle() val roomType: Conversation.ConversationType if (isPublicCall) { roomType = Conversation.ConversationType.PUBLIC_CONVERSATION } else { roomType = Conversation.ConversationType.GROUP_CONVERSATION } val userIdsArray = ArrayList(selectedUserIds) val groupIdsArray = ArrayList(selectedGroupIds) bundle.putParcelable( BundleKeys.KEY_CONVERSATION_TYPE, Parcels.wrap(roomType) ) bundle.putStringArrayList(BundleKeys.KEY_INVITED_PARTICIPANTS, userIdsArray) bundle.putStringArrayList(BundleKeys.KEY_INVITED_GROUP, groupIdsArray) bundle.putInt(BundleKeys.KEY_OPERATION_CODE, 11) prepareAndShowBottomSheetWithBundle(bundle, true) } } else { val userIdsArray = selectedUserIds.toTypedArray() val groupIdsArray = selectedGroupIds.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) val addParticipantsToConversationWorker = OneTimeWorkRequest.Builder(AddParticipantsToConversation::class.java) .setInputData( data.build() ) .build() WorkManager.getInstance() .enqueue(addParticipantsToConversationWorker) router.popCurrentController() } } private fun initSearchView() { if (activity != null) { val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager if (searchItem != null) { searchView = MenuItemCompat.getActionView(searchItem!!) as SearchView searchView!!.maxWidth = Integer.MAX_VALUE searchView!!.inputType = InputType.TYPE_TEXT_VARIATION_FILTER var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) { 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 { when (item.itemId) { android.R.id.home -> { router.popCurrentController() return true } R.id.contacts_selection_done -> { selectionDone() return true } else -> return 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()) { searchItem!!.expandActionView() searchView!!.setQuery(adapter!!.getFilter(String::class.java) as CharSequence?, false) } } private fun fetchData(startFromScratch: Boolean) { alreadyFetching = true val autocompleteUsersHashSet = HashSet() userHeaderItems = HashMap() val query = adapter!!.getFilter(String::class.java) val retrofitBucket: RetrofitBucket = ApiUtils.getRetrofitBucketForContactsSearchFor14(currentUser!!.baseUrl, query) val modifiedQueryMap = HashMap(retrofitBucket.queryMap) modifiedQueryMap["limit"] = 100 var shareTypesList: MutableList = mutableListOf() // user shareTypesList.add("0") // group shareTypesList.add("1") // remote shareTypesList.add("7"); if (!isNewConversationView) { modifiedQueryMap["itemId"] = "difz" shareTypesList.add("4") } modifiedQueryMap["shareTypes[]"] = shareTypesList ncApi.getContactsWithSearchParam( credentials, retrofitBucket.url, shareTypesList, modifiedQueryMap ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(3) .`as`(AutoDispose.autoDisposable(scopeProvider)) .subscribe(object : Observer { override fun onSubscribe(d: Disposable) {} override fun onNext(responseBody: ResponseBody) { var participant: Participant val newUserItemList = ArrayList>() try { val autocompleteOverall = LoganSquare.parse(responseBody.string(), AutocompleteOverall::class.java) autocompleteUsersHashSet.addAll(autocompleteOverall.ocs.data) for (autocompleteUser in autocompleteUsersHashSet) { if (autocompleteUser.id != currentUser!!.userId && !existingParticipants!!.contains( autocompleteUser.id ) ) { participant = Participant() participant.userId = autocompleteUser.id participant.displayName = autocompleteUser.label participant.source = autocompleteUser.source val headerTitle: String if (autocompleteUser.source != "groups") { headerTitle = participant.displayName .substring(0, 1) .toUpperCase() } else { headerTitle = resources!!.getString(R.string.nc_groups) } val genericTextHeaderItem: GenericTextHeaderItem if (!userHeaderItems.containsKey(headerTitle)) { genericTextHeaderItem = GenericTextHeaderItem(headerTitle) userHeaderItems[headerTitle] = genericTextHeaderItem } val newContactItem = UserItem( participant, currentUser!!, userHeaderItems[headerTitle], activity!! ) if (!contactItems!!.contains(newContactItem)) { newUserItemList.add(newContactItem) } } } } catch (exception: Exception) { Log.e(TAG, "Parsing response body failed while getting contacts") } userHeaderItems = HashMap() contactItems!!.addAll(newUserItemList) newUserItemList.sortWith(Comparator { o1, o2 -> val firstName: String val secondName: String if (o1 is UserItem) { firstName = o1.model.displayName } else { firstName = (o1 as GenericTextHeaderItem).model } if (o2 is UserItem) { secondName = o2.model.displayName } else { secondName = (o2 as GenericTextHeaderItem).model } if (o1 is UserItem && o2 is UserItem) { if ("groups" == o1.model.source && "groups" == o2.model.source) { firstName.compareTo(secondName, ignoreCase = true) } else if ("groups" == o1.model.source) { -1 } else if ("groups" == o2.model.source) { 1 } } firstName.compareTo(secondName, ignoreCase = true) }) contactItems!!.sortWith(Comparator { o1, o2 -> val firstName: String val secondName: String if (o1 is UserItem) { firstName = o1.model.displayName } else { firstName = (o1 as GenericTextHeaderItem).model } if (o2 is UserItem) { secondName = o2.model.displayName } else { secondName = (o2 as GenericTextHeaderItem).model } if (o1 is UserItem && o2 is UserItem) { if ("groups" == o1.model.source && "groups" == o2.model.source) { firstName.compareTo(secondName, ignoreCase = true) } else if ("groups" == o1.model.source) { -1 } else if ("groups" == o2.model.source) { 1 } } firstName.compareTo(secondName, ignoreCase = true) }) if (newUserItemList.size > 0) { adapter!!.updateDataSet(newUserItemList) } else { adapter!!.filterItems() } if (swipeRefreshLayout != null) { swipeRefreshLayout!!.isRefreshing = false } } override fun onError(e: Throwable) { if (swipeRefreshLayout != null) { swipeRefreshLayout!!.isRefreshing = false } } override fun onComplete() { if (swipeRefreshLayout != null) { swipeRefreshLayout!!.isRefreshing = false } alreadyFetching = false disengageProgressBar() } }) } private fun prepareViews() { layoutManager = SmoothScrollLinearLayoutManager(activity!!) recyclerView?.layoutManager = layoutManager recyclerView?.setHasFixedSize(true) recyclerView?.adapter = adapter adapter!!.setStickyHeaderElevation(5) .setUnlinkAllItemsOnRemoveHeaders(true) .setDisplayHeadersAtStartUp(true) .setStickyHeaders(true) swipeRefreshLayout!!.setOnRefreshListener { fetchData(true) } swipeRefreshLayout!!.setColorSchemeResources(R.color.colorPrimary) fastScroller!!.addOnScrollStateChangeListener(this) adapter!!.fastScroller = fastScroller fastScroller!!.setBubbleTextCreator { position -> val abstractFlexibleItem = adapter!!.getItem(position) if (abstractFlexibleItem is UserItem) { (adapter!!.getItem(position) as UserItem).header!!.model } else if (abstractFlexibleItem is GenericTextHeaderItem) { (adapter!!.getItem(position) as GenericTextHeaderItem).model } else { "" } } disengageProgressBar() } private fun disengageProgressBar() { if (!alreadyFetching) { progressBar!!.visibility = View.GONE genericRvLayout!!.visibility = View.VISIBLE if (isNewConversationView) { conversationPrivacyToogleLayout!!.visibility = View.VISIBLE joinConversationViaLinkLayout!!.visibility = View.VISIBLE } } } public override fun onSaveViewState( view: View, outState: Bundle ) { adapter!!.onSaveInstanceState(outState) super.onSaveViewState(view, outState) } public override fun onRestoreViewState( view: View, savedViewState: Bundle ) { super.onRestoreViewState(view, savedViewState) if (adapter != null) { adapter!!.onRestoreInstanceState(savedViewState) } } override fun onQueryTextChange(newText: String): Boolean { if (newText != "" && adapter!!.hasNewFilter(newText)) { adapter!!.setFilter(newText) fetchData(true) } else if (newText == "") { adapter!!.setFilter("") adapter!!.updateDataSet(contactItems) } if (swipeRefreshLayout != null) { swipeRefreshLayout!!.isEnabled = !adapter!!.hasFilter() } return true } override fun onQueryTextSubmit(query: String): Boolean { return onQueryTextChange(query) } private fun checkAndHandleDoneMenuItem() { if (adapter != null && doneMenuItem != null) { doneMenuItem!!.isVisible = selectedGroupIds.size + selectedUserIds.size > 0 || isPublicCall } else if (doneMenuItem != null) { doneMenuItem!!.isVisible = false } } override fun getTitle(): String? { return if (!isNewConversationView && !isAddingParticipantsView) { resources!!.getString(R.string.nc_app_name) } else { resources!!.getString(R.string.nc_select_contacts) } } override fun onFastScrollerStateChange(scrolling: Boolean) { swipeRefreshLayout!!.isEnabled = !scrolling } private fun prepareAndShowBottomSheetWithBundle( bundle: Bundle, showEntrySheet: Boolean ) { if (bottomSheetView == null) { bottomSheetView = activity!!.layoutInflater.inflate(R.layout.bottom_sheet, null, false) } if (bottomSheet == null) { bottomSheet = BottomSheet.Builder(activity!!) .setView(bottomSheetView) .create() } if (showEntrySheet) { getChildRouter((bottomSheetView as ViewGroup?)!!).setRoot( RouterTransaction.with(EntryMenuController(bundle)) .popChangeHandler(VerticalChangeHandler()) .pushChangeHandler(VerticalChangeHandler()) ) } else { getChildRouter((bottomSheetView as ViewGroup?)!!).setRoot( RouterTransaction.with(OperationsMenuController(bundle)) .popChangeHandler(VerticalChangeHandler()) .pushChangeHandler(VerticalChangeHandler()) ) } bottomSheet!!.setOnShowListener { dialog -> if (showEntrySheet) { KeyboardUtils(activity!!, bottomSheet!!.layout, true) } else { eventBus.post( BottomSheetLockEvent( false, 0, false, false ) ) } } bottomSheet!!.setOnDismissListener { dialog -> actionBar!!.setDisplayHomeAsUpEnabled( router.backstackSize > 1 ) } bottomSheet!!.show() } @Subscribe(threadMode = ThreadMode.MAIN) fun onMessageEvent(bottomSheetLockEvent: BottomSheetLockEvent) { if (bottomSheet != null) { if (!bottomSheetLockEvent.cancelable) { bottomSheet!!.setCancelable(bottomSheetLockEvent.cancelable) } else { bottomSheet!!.setCancelable(bottomSheetLockEvent.cancelable) if (bottomSheet!!.isShowing && bottomSheetLockEvent.cancel) { Handler().postDelayed({ bottomSheet!!.setOnCancelListener(null) bottomSheet!!.cancel() }, bottomSheetLockEvent.delay.toLong()) } } } } override fun onItemClick( view: View, position: Int ): Boolean { if (adapter!!.getItem(position) is UserItem) { if (!isNewConversationView && !isAddingParticipantsView) { val userItem = adapter!!.getItem(position) as UserItem? var roomType = "1" if ("groups" == userItem!!.model.source) { roomType = "2" } val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom( currentUser!!.baseUrl, roomType, userItem.model.userId, null ) ncApi.createRoom( credentials, retrofitBucket.url, retrofitBucket.queryMap ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .`as`(AutoDispose.autoDisposable(scopeProvider)) .subscribe(object : Observer { override fun onSubscribe(d: Disposable) { } override fun onNext(roomOverall: RoomOverall) { if (activity != null) { val conversationIntent = Intent(activity, MagicCallActivity::class.java) val bundle = Bundle() bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, currentUser) bundle.putString( BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs.data.token ) bundle.putString( BundleKeys.KEY_ROOM_ID, roomOverall.ocs.data.conversationId ) conversationIntent.putExtras(bundle) if (currentUser!!.hasSpreedFeatureCapability("chat-v2")) { bundle.putParcelable( BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(roomOverall.ocs.data) ) ConductorRemapping.remapChatController( router, currentUser!!.id!!, roomOverall.ocs.data.token!!, bundle, true ) } else { startActivity(conversationIntent) Handler().postDelayed({ router.popCurrentController() }, 100) } } } override fun onError(e: Throwable) { } override fun onComplete() { } }) } else { val participant = (adapter!!.getItem(position) as UserItem).model participant.selected = !participant.selected if ("groups" == participant.source) { if (participant.selected) { selectedGroupIds.add(participant.userId) } else { selectedGroupIds.remove(participant.userId) } } else { if (participant.selected) { selectedUserIds.add(participant.userId) } else { selectedUserIds.remove(participant.userId) } } if (currentUser!!.hasSpreedFeatureCapability("last-room-activity") && !currentUser!!.hasSpreedFeatureCapability("invite-groups-and-mails") && "groups" == (adapter!!.getItem(position) as UserItem).model.source && participant.selected && adapter!!.selectedItemCount > 1 ) { val currentItems = adapter!!.currentItems var internalParticipant: Participant for (i in currentItems.indices) { if (currentItems[i] is UserItem) { internalParticipant = (currentItems[i] as UserItem).model if (internalParticipant.userId == participant.userId && "groups" == internalParticipant.source && internalParticipant.selected ) { internalParticipant.selected = false selectedGroupIds.remove(internalParticipant.userId) } } } } adapter!!.notifyDataSetChanged() checkAndHandleDoneMenuItem() } } return true } @Optional @OnClick(R.id.joinConversationViaLinkRelativeLayout) internal fun joinConversationViaLink() { val bundle = Bundle() bundle.putInt(BundleKeys.KEY_OPERATION_CODE, 10) prepareAndShowBottomSheetWithBundle(bundle, true) } @Optional @OnClick(R.id.call_header_layout) internal fun toggleCallHeader() { toggleNewCallHeaderVisibility(isPublicCall) isPublicCall = !isPublicCall if (isPublicCall) { joinConversationViaLinkLayout!!.visibility = View.GONE } else { joinConversationViaLinkLayout!!.visibility = View.VISIBLE } if (isPublicCall) { val currentItems = adapter!!.currentItems var internalParticipant: Participant for (i in currentItems.indices) { if (currentItems[i] is UserItem) { internalParticipant = (currentItems[i] as UserItem).model if ("groups" == internalParticipant.source && internalParticipant.selected) { internalParticipant.selected = false selectedGroupIds.remove(internalParticipant.userId) } } } } for (i in 0 until adapter!!.itemCount) { if (adapter!!.getItem(i) is UserItem) { val userItem = adapter!!.getItem(i) as UserItem? if ("groups" == userItem!!.model.source) { userItem.isEnabled = !isPublicCall } } } checkAndHandleDoneMenuItem() adapter!!.notifyDataSetChanged() } private fun toggleNewCallHeaderVisibility(showInitialLayout: Boolean) { if (showInitialLayout) { initialRelativeLayout!!.visibility = View.VISIBLE secondaryRelativeLayout!!.visibility = View.GONE } else { initialRelativeLayout!!.visibility = View.GONE secondaryRelativeLayout!!.visibility = View.VISIBLE } } companion object { val TAG = "ContactsController" } }