mirror of
https://github.com/nextcloud/talk-android
synced 2025-02-02 20:53:09 +00:00
Merge commit 'dc705ccf4027f61ebaefe625a99087cf5641d6a7'
This commit is contained in:
commit
8bc3f80667
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -357,6 +357,7 @@ class ProfileController : NewBaseController(R.layout.controller_profile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("Detekt.LongMethod")
|
||||||
private fun createUserInfoDetails(userInfo: UserProfileData?): List<UserInfoDetailsItem> {
|
private fun createUserInfoDetails(userInfo: UserProfileData?): List<UserInfoDetailsItem> {
|
||||||
val result: MutableList<UserInfoDetailsItem> = LinkedList()
|
val result: MutableList<UserInfoDetailsItem> = LinkedList()
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.GUESTS
|
|||||||
import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
|
import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
|
||||||
|
|
||||||
class EnumActorTypeConverter : StringBasedTypeConverter<Participant.ActorType>() {
|
class EnumActorTypeConverter : StringBasedTypeConverter<Participant.ActorType>() {
|
||||||
override fun getFromString(string: String): Participant.ActorType {
|
override fun getFromString(string: String?): Participant.ActorType {
|
||||||
return when (string) {
|
return when (string) {
|
||||||
"emails" -> EMAILS
|
"emails" -> EMAILS
|
||||||
"groups" -> GROUPS
|
"groups" -> GROUPS
|
||||||
|
@ -121,53 +121,53 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_RE
|
|||||||
*/
|
*/
|
||||||
class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.SystemMessageType>() {
|
class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.SystemMessageType>() {
|
||||||
override fun getFromString(string: String): ChatMessage.SystemMessageType {
|
override fun getFromString(string: String): ChatMessage.SystemMessageType {
|
||||||
when (string) {
|
return when (string) {
|
||||||
"conversation_created" -> return CONVERSATION_CREATED
|
"conversation_created" -> CONVERSATION_CREATED
|
||||||
"conversation_renamed" -> return CONVERSATION_RENAMED
|
"conversation_renamed" -> CONVERSATION_RENAMED
|
||||||
"description_set" -> return DESCRIPTION_SET
|
"description_set" -> DESCRIPTION_SET
|
||||||
"description_removed" -> return DESCRIPTION_REMOVED
|
"description_removed" -> DESCRIPTION_REMOVED
|
||||||
"call_started" -> return CALL_STARTED
|
"call_started" -> CALL_STARTED
|
||||||
"call_joined" -> return CALL_JOINED
|
"call_joined" -> CALL_JOINED
|
||||||
"call_left" -> return CALL_LEFT
|
"call_left" -> CALL_LEFT
|
||||||
"call_ended" -> return CALL_ENDED
|
"call_ended" -> CALL_ENDED
|
||||||
"call_ended_everyone" -> return CALL_ENDED_EVERYONE
|
"call_ended_everyone" -> CALL_ENDED_EVERYONE
|
||||||
"call_missed" -> return CALL_MISSED
|
"call_missed" -> CALL_MISSED
|
||||||
"call_tried" -> return CALL_TRIED
|
"call_tried" -> CALL_TRIED
|
||||||
"read_only_off" -> return READ_ONLY_OFF
|
"read_only_off" -> READ_ONLY_OFF
|
||||||
"read_only" -> return READ_ONLY
|
"read_only" -> READ_ONLY
|
||||||
"listable_none" -> return LISTABLE_NONE
|
"listable_none" -> LISTABLE_NONE
|
||||||
"listable_users" -> return LISTABLE_USERS
|
"listable_users" -> LISTABLE_USERS
|
||||||
"listable_all" -> return LISTABLE_ALL
|
"listable_all" -> LISTABLE_ALL
|
||||||
"lobby_none" -> return LOBBY_NONE
|
"lobby_none" -> LOBBY_NONE
|
||||||
"lobby_non_moderators" -> return LOBBY_NON_MODERATORS
|
"lobby_non_moderators" -> LOBBY_NON_MODERATORS
|
||||||
"lobby_timer_reached" -> return LOBBY_OPEN_TO_EVERYONE
|
"lobby_timer_reached" -> LOBBY_OPEN_TO_EVERYONE
|
||||||
"guests_allowed" -> return GUESTS_ALLOWED
|
"guests_allowed" -> GUESTS_ALLOWED
|
||||||
"guests_disallowed" -> return GUESTS_DISALLOWED
|
"guests_disallowed" -> GUESTS_DISALLOWED
|
||||||
"password_set" -> return PASSWORD_SET
|
"password_set" -> PASSWORD_SET
|
||||||
"password_removed" -> return PASSWORD_REMOVED
|
"password_removed" -> PASSWORD_REMOVED
|
||||||
"user_added" -> return USER_ADDED
|
"user_added" -> USER_ADDED
|
||||||
"user_removed" -> return USER_REMOVED
|
"user_removed" -> USER_REMOVED
|
||||||
"group_added" -> return GROUP_ADDED
|
"group_added" -> GROUP_ADDED
|
||||||
"group_removed" -> return GROUP_REMOVED
|
"group_removed" -> GROUP_REMOVED
|
||||||
"circle_added" -> return CIRCLE_ADDED
|
"circle_added" -> CIRCLE_ADDED
|
||||||
"circle_removed" -> return CIRCLE_REMOVED
|
"circle_removed" -> CIRCLE_REMOVED
|
||||||
"moderator_promoted" -> return MODERATOR_PROMOTED
|
"moderator_promoted" -> MODERATOR_PROMOTED
|
||||||
"moderator_demoted" -> return MODERATOR_DEMOTED
|
"moderator_demoted" -> MODERATOR_DEMOTED
|
||||||
"guest_moderator_promoted" -> return GUEST_MODERATOR_PROMOTED
|
"guest_moderator_promoted" -> GUEST_MODERATOR_PROMOTED
|
||||||
"guest_moderator_demoted" -> return GUEST_MODERATOR_DEMOTED
|
"guest_moderator_demoted" -> GUEST_MODERATOR_DEMOTED
|
||||||
"message_deleted" -> return MESSAGE_DELETED
|
"message_deleted" -> MESSAGE_DELETED
|
||||||
"file_shared" -> return FILE_SHARED
|
"file_shared" -> FILE_SHARED
|
||||||
"object_shared" -> return OBJECT_SHARED
|
"object_shared" -> OBJECT_SHARED
|
||||||
"matterbridge_config_added" -> return MATTERBRIDGE_CONFIG_ADDED
|
"matterbridge_config_added" -> MATTERBRIDGE_CONFIG_ADDED
|
||||||
"matterbridge_config_edited" -> return MATTERBRIDGE_CONFIG_EDITED
|
"matterbridge_config_edited" -> MATTERBRIDGE_CONFIG_EDITED
|
||||||
"matterbridge_config_removed" -> return MATTERBRIDGE_CONFIG_REMOVED
|
"matterbridge_config_removed" -> MATTERBRIDGE_CONFIG_REMOVED
|
||||||
"matterbridge_config_enabled" -> return MATTERBRIDGE_CONFIG_ENABLED
|
"matterbridge_config_enabled" -> MATTERBRIDGE_CONFIG_ENABLED
|
||||||
"matterbridge_config_disabled" -> return MATTERBRIDGE_CONFIG_DISABLED
|
"matterbridge_config_disabled" -> MATTERBRIDGE_CONFIG_DISABLED
|
||||||
"history_cleared" -> return CLEARED_CHAT
|
"history_cleared" -> CLEARED_CHAT
|
||||||
"reaction" -> return REACTION
|
"reaction" -> REACTION
|
||||||
"reaction_deleted" -> return REACTION_DELETED
|
"reaction_deleted" -> REACTION_DELETED
|
||||||
"reaction_revoked" -> return REACTION_REVOKED
|
"reaction_revoked" -> REACTION_REVOKED
|
||||||
else -> return DUMMY
|
else -> DUMMY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,10 +162,8 @@ class MessageActionsDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initEmojiBar(hasChatPermission: Boolean) {
|
private fun initEmojiBar(hasChatPermission: Boolean) {
|
||||||
if (hasChatPermission &&
|
if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "reactions") &&
|
||||||
CapabilitiesUtil.hasSpreedFeatureCapability(user, "reactions") &&
|
isPermitted(hasChatPermission) &&
|
||||||
Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
|
|
||||||
currentConversation?.conversationReadOnlyState &&
|
|
||||||
isReactableMessageType(message)
|
isReactableMessageType(message)
|
||||||
) {
|
) {
|
||||||
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsUp)
|
checkAndSetEmojiSelfReaction(dialogMessageActionsBinding.emojiThumbsUp)
|
||||||
@ -203,6 +201,11 @@ class MessageActionsDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isPermitted(hasChatPermission: Boolean): Boolean {
|
||||||
|
return hasChatPermission && Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
|
||||||
|
currentConversation?.conversationReadOnlyState
|
||||||
|
}
|
||||||
|
|
||||||
private fun isReactableMessageType(message: ChatMessage): Boolean {
|
private fun isReactableMessageType(message: ChatMessage): Boolean {
|
||||||
return !(message.isCommandMessage || message.isDeletedCommentMessage || message.isDeleted)
|
return !(message.isCommandMessage || message.isDeletedCommentMessage || message.isDeleted)
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import java.util.HashMap
|
|||||||
|
|
||||||
object DrawableUtils {
|
object DrawableUtils {
|
||||||
|
|
||||||
|
@Suppress("Detekt.LongMethod")
|
||||||
fun getDrawableResourceIdForMimeType(mimetype: String?): Int {
|
fun getDrawableResourceIdForMimeType(mimetype: String?): Int {
|
||||||
var localMimetype = mimetype
|
var localMimetype = mimetype
|
||||||
val drawableMap = HashMap<String, Int>()
|
val drawableMap = HashMap<String, Int>()
|
||||||
|
@ -62,14 +62,17 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
android:id="@+id/conversation_privacy_toggle"
|
||||||
layout="@layout/conversation_privacy_toggle"
|
layout="@layout/conversation_privacy_toggle"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
android:id="@+id/join_conversation_via_link"
|
||||||
layout="@layout/join_conversation_via_link"
|
layout="@layout/join_conversation_via_link"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
|
android:id="@+id/controller_generic_rv"
|
||||||
layout="@layout/controller_generic_rv"
|
layout="@layout/controller_generic_rv"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
Loading…
Reference in New Issue
Block a user