From 439ea42b54458f9a764a8b31b725e78e9727bae1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 22 Dec 2020 16:32:32 +0100 Subject: [PATCH] VoIP: use UserListFragment to select someone for call transfer (+ clean some code) --- .../java/im/vector/app/core/extensions/Set.kt | 14 +++- .../app/core/platform/VectorBaseActivity.kt | 15 ++++ .../features/call/CallControlsBottomSheet.kt | 6 ++ .../app/features/call/VectorCallActivity.kt | 9 ++- .../features/call/VectorCallViewActions.kt | 1 + .../app/features/call/VectorCallViewEvents.kt | 1 + .../app/features/call/VectorCallViewModel.kt | 12 ++- .../app/features/call/VectorCallViewState.kt | 3 +- .../call/transfer/CallTransferAction.kt | 23 ++++++ .../call/transfer/CallTransferActivity.kt | 58 ++++++++++----- .../call/transfer/CallTransferViewEvents.kt | 23 ++++++ .../call/transfer/CallTransferViewModel.kt | 54 ++++++++++++-- .../contactsbook/ContactsBookFragment.kt | 6 +- .../createdirect/CreateDirectRoomAction.kt | 4 +- .../createdirect/CreateDirectRoomActivity.kt | 2 +- .../CreateDirectRoomByQrCodeFragment.kt | 5 +- .../createdirect/CreateDirectRoomViewModel.kt | 13 ++-- .../invite/InviteUsersToRoomAction.kt | 4 +- .../invite/InviteUsersToRoomActivity.kt | 5 +- .../invite/InviteUsersToRoomViewModel.kt | 27 ++++--- ...{PendingInvitee.kt => PendingSelection.kt} | 10 +-- .../features/userdirectory/UserListAction.kt | 4 +- .../userdirectory/UserListController.kt | 9 +-- .../userdirectory/UserListFragment.kt | 38 ++++++---- .../userdirectory/UserListFragmentArgs.kt | 4 +- .../userdirectory/UserListSharedAction.kt | 2 +- .../userdirectory/UserListViewModel.kt | 26 +++---- .../userdirectory/UserListViewState.kt | 18 +++-- .../main/res/drawable/ic_call_transfer.xml | 17 +++++ .../res/layout/activity_call_transfer.xml | 73 +++++++++++++++++++ .../res/layout/bottom_sheet_call_controls.xml | 9 +++ vector/src/main/res/values/strings.xml | 4 + 32 files changed, 376 insertions(+), 123 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt rename vector/src/main/java/im/vector/app/features/userdirectory/{PendingInvitee.kt => PendingSelection.kt} (73%) create mode 100644 vector/src/main/res/drawable/ic_call_transfer.xml create mode 100644 vector/src/main/res/layout/activity_call_transfer.xml diff --git a/vector/src/main/java/im/vector/app/core/extensions/Set.kt b/vector/src/main/java/im/vector/app/core/extensions/Set.kt index a78fb85a1d..e1787076b9 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Set.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Set.kt @@ -17,10 +17,18 @@ package im.vector.app.core.extensions // Create a new Set including the provided element if not already present, or removing the element if already present -fun Set.toggle(element: T): Set { +fun Set.toggle(element: T, singleElement: Boolean = false): Set { return if (contains(element)) { - minus(element) + if (singleElement) { + emptySet() + } else { + minus(element) + } } else { - plus(element) + if (singleElement) { + setOf(element) + } else { + plus(element) + } } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index a585e8ea77..268ec7eb26 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -42,6 +42,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding import com.bumptech.glide.util.Util import com.google.android.material.snackbar.Snackbar +import com.jakewharton.rxbinding3.view.clicks import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -84,6 +85,7 @@ import io.reactivex.disposables.Disposable import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import timber.log.Timber +import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { @@ -113,6 +115,19 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScr .disposeOnDestroy() } + /* ========================================================================================== + * Views + * ========================================================================================== */ + + protected fun View.debouncedClicks(onClicked: () -> Unit) { + clicks() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { onClicked() } + .disposeOnDestroy() + } + + /* ========================================================================================== * DATA * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index a94a030e34..eab788f461 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -63,6 +63,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment { @@ -153,5 +158,6 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment(), CallContro views.callConnectingProgress.isVisible = true configureCallInfo(state) } - is CallState.Connected -> { + is CallState.Connected -> { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { if (state.isLocalOnHold || state.isRemoteOnHold) { views.smallIsHeldIcon.isVisible = true @@ -227,10 +227,10 @@ class VectorCallActivity : VectorBaseActivity(), CallContro views.callConnectingProgress.isVisible = true } } - is CallState.Terminated -> { + is CallState.Terminated -> { finish() } - null -> { + null -> { } } } @@ -320,6 +320,9 @@ class VectorCallActivity : VectorBaseActivity(), CallContro is VectorCallViewEvents.ConnectionTimeout -> { onErrorTimoutConnect(event.turn) } + is VectorCallViewEvents.ShowCallTransferScreen -> { + navigator.openCallTransfer(this, callArgs.callId) + } null -> { } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index 83ac878186..adb8897a51 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -30,4 +30,5 @@ sealed class VectorCallViewActions : VectorViewModelAction { object HeadSetButtonPressed : VectorCallViewActions() object ToggleCamera : VectorCallViewActions() object ToggleHDSD : VectorCallViewActions() + object InitiateCallTransfer : VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index b79cd5d772..832c4fe944 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -27,6 +27,7 @@ sealed class VectorCallViewEvents : VectorViewEvents { val available: List, val current: CallAudioManager.SoundDevice ) : VectorCallViewEvents() + object ShowCallTransferScreen: VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 6f5a9213ae..0c621dcfe4 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.call.TurnServerResponse +import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import java.util.Timer @@ -114,7 +115,8 @@ class VectorCallViewModel @AssistedInject constructor( } setState { copy( - callState = Success(callState) + callState = Success(callState), + canOpponentBeTransferred = call.capabilities.supportCallTransfer() ) } } @@ -188,7 +190,8 @@ class VectorCallViewModel @AssistedInject constructor( isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT, canSwitchCamera = webRtcCall.canSwitchCamera(), formattedDuration = webRtcCall.formattedDuration(), - isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD + isHD = webRtcCall.mxCall.isVideoCall && webRtcCall.currentCaptureFormat() is CaptureFormat.HD, + canOpponentBeTransferred = webRtcCall.mxCall.capabilities.supportCallTransfer() ) } updateOtherKnownCall(webRtcCall) @@ -269,6 +272,11 @@ class VectorCallViewModel @AssistedInject constructor( if (!state.isVideoCall) return@withState call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD) } + VectorCallViewActions.InitiateCallTransfer -> { + _viewEvents.post( + VectorCallViewEvents.ShowCallTransferScreen + ) + } }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 15fa2a37fa..1d12b780b1 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -39,7 +39,8 @@ data class VectorCallViewState( val callState: Async = Uninitialized, val otherKnownCallInfo: CallInfo? = null, val callInfo: CallInfo = CallInfo(callId), - val formattedDuration: String = "" + val formattedDuration: String = "", + val canOpponentBeTransferred: Boolean = false ) : MvRxState { data class CallInfo( diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt new file mode 100644 index 0000000000..cbdd9c252b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.call.transfer + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class CallTransferAction : VectorViewModelAction { + data class Connect(val consultFirst: Boolean, val selectedUserId: String) : CallTransferAction() +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt index d038a0e836..ec517c813b 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -20,19 +20,20 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable -import android.view.View import android.widget.Toast import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.viewModel import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.checkPermissions +import im.vector.app.databinding.ActivityCallTransferBinding import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel import im.vector.app.features.contactsbook.ContactsBookViewState @@ -48,7 +49,9 @@ import javax.inject.Inject @Parcelize data class CallTransferArgs(val callId: String) : Parcelable -class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Factory, UserListViewModel.Factory, ContactsBookViewModel.Factory { +private const val USER_LIST_FRAGMENT_TAG = "USER_LIST_FRAGMENT_TAG" + +class CallTransferActivity : VectorBaseActivity(), CallTransferViewModel.Factory, UserListViewModel.Factory, ContactsBookViewModel.Factory { private lateinit var sharedActionViewModel: UserListSharedActionViewModel @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory @@ -56,6 +59,10 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter + private val callTransferViewModel: CallTransferViewModel by viewModel() + + override fun getBinding() = ActivityCallTransferBinding.inflate(layoutInflater) + override fun injectWith(injector: ScreenComponent) { super.injectWith(injector) injector.inject(this) @@ -75,33 +82,50 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - views.toolbar.visibility = View.GONE - sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel .observe() .subscribe { sharedAction -> when (sharedAction) { - UserListSharedAction.Close -> finish() - UserListSharedAction.GoBack -> onBackPressed() - UserListSharedAction.OpenPhoneBook -> openPhoneBook() + UserListSharedAction.OpenPhoneBook -> openPhoneBook() // not exhaustive because it's a sharedAction - else -> { + else -> { } } } .disposeOnDestroy() if (isFirstCreation()) { addFragment( - R.id.container, + R.id.callTransferFragmentContainer, UserListFragment::class.java, UserListFragmentArgs( - title = "Call transfer", - menuResId = -1 - ) + title = "", + menuResId = -1, + singleSelection = true, + showInviteActions = false, + showToolbar = false + ), + USER_LIST_FRAGMENT_TAG ) } + callTransferViewModel.observeViewEvents { + when (it) { + is CallTransferViewEvents.Dismiss -> finish() + } + } + configureToolbar(views.callTransferToolbar) + setupConnectAction() + } + + private fun setupConnectAction() { + views.callTransferConnectAction.debouncedClicks { + val userListFragment = supportFragmentManager.findFragmentByTag(USER_LIST_FRAGMENT_TAG) as? UserListFragment + val selectedUser = userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() + if (selectedUser != null) { + val action = CallTransferAction.Connect(views.callTransferConsultCheckBox.isChecked, selectedUser) + callTransferViewModel.handle(action) + } + } } private fun openPhoneBook() { @@ -110,7 +134,7 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac this, PERMISSION_REQUEST_CODE_READ_CONTACTS, 0)) { - addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) + addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) } } @@ -118,7 +142,7 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (allGranted(grantResults)) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { - doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } + doOnPostResume { addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) } } } else { Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() @@ -126,7 +150,7 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac } companion object { - + fun newIntent(context: Context, callId: String): Intent { return Intent(context, CallTransferActivity::class.java).also { it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId)) diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt new file mode 100644 index 0000000000..b4a228efca --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.call.transfer + +import im.vector.app.core.platform.VectorViewEvents + +sealed class CallTransferViewEvents : VectorViewEvents { + object Dismiss : CallTransferViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index fd9557315c..78c7cc4664 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -16,20 +16,23 @@ package im.vector.app.features.call.transfer +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import im.vector.app.core.platform.EmptyAction -import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.session.Session +import im.vector.app.features.call.webrtc.WebRtcCall +import im.vector.app.features.call.webrtc.WebRtcCallManager +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.call.CallState +import org.matrix.android.sdk.api.session.call.MxCall import timber.log.Timber -class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState) - : VectorViewModel(initialState) { +class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState, + private val callManager: WebRtcCallManager) + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -45,6 +48,43 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: } } - override fun handle(action: EmptyAction) {} + private var call: WebRtcCall? = null + private val callListener = object : WebRtcCall.Listener { + override fun onStateUpdate(call: MxCall) { + if (call.state == CallState.Terminated) { + _viewEvents.post(CallTransferViewEvents.Dismiss) + } + } + } + init { + val webRtcCall = callManager.getCallById(initialState.callId) + if (webRtcCall == null) { + _viewEvents.post(CallTransferViewEvents.Dismiss) + } else { + call = webRtcCall + webRtcCall.addListener(callListener) + } + } + + override fun onCleared() { + super.onCleared() + call?.removeListener(callListener) + } + + override fun handle(action: CallTransferAction) { + when (action) { + is CallTransferAction.Connect -> transferCall(action) + } + } + + private fun transferCall(action: CallTransferAction.Connect) { + viewModelScope.launch { + try { + call?.mxCall?.transfer(action.selectedUserId, null) + } catch (failure: Throwable) { + Timber.v("Fail to transfer call: $failure") + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index 3e85702cf4..68e169b8c5 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -32,7 +32,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentContactsBookBinding -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import im.vector.app.features.userdirectory.UserListAction import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel @@ -132,13 +132,13 @@ class ContactsBookFragment @Inject constructor( override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId)))) sharedActionViewModel.post(UserListSharedAction.GoBack) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid))) sharedActionViewModel.post(UserListSharedAction.GoBack) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt index ce91761fdd..ffc25210e9 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt @@ -17,11 +17,11 @@ package im.vector.app.features.createdirect import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection sealed class CreateDirectRoomAction : VectorViewModelAction { data class CreateRoomAndInviteSelectedUsers( - val invitees: Set, + val selections: Set, val existingDmRoomId: String? ) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 347424ee8a..4f81841b73 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -146,7 +146,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers( - action.invitees, + action.selections, null )) } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt index 94578ed5c7..92a03c5483 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt @@ -29,8 +29,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentQrCodeScannerBinding -import im.vector.app.features.userdirectory.PendingInvitee - +import im.vector.app.features.userdirectory.PendingSelection import me.dm7.barcodescanner.zxing.ZXingScannerView import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser @@ -107,7 +106,7 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null) viewModel.handle( - CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm) + CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingSelection.UserPendingSelection(qrInvitee)), existingDm) ) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index fdf16f2664..9ac8ed2450 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -26,10 +26,9 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.contactsbook.ContactsBookViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.raw.RawService @@ -77,11 +76,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } } else { // Create the DM - createRoomAndInviteSelectedUsers(action.invitees) + createRoomAndInviteSelectedUsers(action.selections) } } - private fun createRoomAndInviteSelectedUsers(invitees: Set) { + private fun createRoomAndInviteSelectedUsers(selections: Set) { viewModelScope.launch(Dispatchers.IO) { val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) ?.isE2EByDefault() @@ -89,10 +88,10 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted val roomParams = CreateRoomParams() .apply { - invitees.forEach { + selections.forEach { when (it) { - is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId) - is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid) + is PendingSelection.UserPendingSelection -> invitedUserIds.add(it.user.userId) + is PendingSelection.ThreePidPendingSelection -> invite3pids.add(it.threePid) }.exhaustive } setDirectMessage() diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt index cd9df8dc96..be9ad61868 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomAction.kt @@ -17,8 +17,8 @@ package im.vector.app.features.invite import im.vector.app.core.platform.VectorViewModelAction -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection sealed class InviteUsersToRoomAction : VectorViewModelAction { - data class InviteSelectedUsers(val invitees: Set) : InviteUsersToRoomAction() + data class InviteSelectedUsers(val selections: Set) : InviteUsersToRoomAction() } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index f923bb7306..f9f5b2b995 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -95,7 +95,6 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa } .disposeOnDestroy() if (isFirstCreation()) { - val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG) addFragment( R.id.container, UserListFragment::class.java, @@ -103,7 +102,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa title = getString(R.string.invite_users_to_room_title), menuResId = R.menu.vector_invite_users_to_room, excludedUserIds = viewModel.getUserIdsOfRoomMembers(), - existingRoomId = args?.roomId + showInviteActions = false ) ) } @@ -113,7 +112,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_invite_users_to_room_invite) { - viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees)) + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections)) } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 984eed33b0..c0e9ea6d51 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -25,8 +25,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.contactsbook.ContactsBookViewModel -import im.vector.app.features.userdirectory.PendingInvitee +import im.vector.app.features.userdirectory.PendingSelection import io.reactivex.Observable import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.rx.rx @@ -58,30 +57,30 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted override fun handle(action: InviteUsersToRoomAction) { when (action) { - is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.invitees) + is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selections) } } - private fun inviteUsersToRoom(invitees: Set) { + private fun inviteUsersToRoom(selections: Set) { _viewEvents.post(InviteUsersToRoomViewEvents.Loading) - Observable.fromIterable(invitees).flatMapCompletable { user -> + Observable.fromIterable(selections).flatMapCompletable { user -> when (user) { - is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null) - is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid) + is PendingSelection.UserPendingSelection -> room.rx().invite(user.user.userId, null) + is PendingSelection.ThreePidPendingSelection -> room.rx().invite3pid(user.threePid) } }.subscribe( { - val successMessage = when (invitees.size) { + val successMessage = when (selections.size) { 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, - invitees.first().getBestName()) + selections.first().getBestName()) 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, - invitees.first().getBestName(), - invitees.last().getBestName()) + selections.first().getBestName(), + selections.last().getBestName()) else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, - invitees.size - 1, - invitees.first().getBestName(), - invitees.size - 1) + selections.size - 1, + selections.first().getBestName(), + selections.size - 1) } _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) }, diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt similarity index 73% rename from vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt index f7213497fa..57f950b2c8 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/PendingInvitee.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/PendingSelection.kt @@ -19,14 +19,14 @@ package im.vector.app.features.userdirectory import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User -sealed class PendingInvitee { - data class UserPendingInvitee(val user: User) : PendingInvitee() - data class ThreePidPendingInvitee(val threePid: ThreePid) : PendingInvitee() +sealed class PendingSelection { + data class UserPendingSelection(val user: User) : PendingSelection() + data class ThreePidPendingSelection(val threePid: ThreePid) : PendingSelection() fun getBestName(): String { return when (this) { - is UserPendingInvitee -> user.getBestName() - is ThreePidPendingInvitee -> threePid.value + is UserPendingSelection -> user.getBestName() + is ThreePidPendingSelection -> threePid.value } } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt index 0c2c4b1f4b..7835232b09 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class UserListAction : VectorViewModelAction { data class SearchUsers(val value: String) : UserListAction() object ClearSearchUsers : UserListAction() - data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() - data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() + data class AddPendingSelection(val pendingSelection: PendingSelection) : UserListAction() + data class RemovePendingSelection(val pendingSelection: PendingSelection) : UserListAction() object ComputeMatrixToLinkForSharing : UserListAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 3e1523d0cc..331329ae61 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -54,10 +54,7 @@ class UserListController @Inject constructor(private val session: Session, // Build generic items if (currentState.searchTerm.isBlank()) { - // For now we remove this option if in invite to existing room flow (and not create DM) - if (currentState.pendingInvitees.isEmpty() - // For now we remove this option if in invite to existing room flow (and not create DM) - && currentState.existingRoomId == null) { + if (currentState.showInviteActions()) { actionItem { id(R.drawable.ic_share) title(stringProvider.getString(R.string.invite_friends)) @@ -75,9 +72,7 @@ class UserListController @Inject constructor(private val session: Session, callback?.onContactBookClick() }) } - if (currentState.pendingInvitees.isEmpty() - // For now we remove this option if in invite to existing room flow (and not create DM) - && currentState.existingRoomId == null) { + if (currentState.showInviteActions()) { actionItem { id(R.drawable.ic_qr_code_add) title(stringProvider.getString(R.string.qr_code)) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index d06030c301..0dca6802c4 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -67,18 +67,22 @@ class UserListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java) - views.userListTitle.text = args.title - vectorBaseActivity.setSupportActionBar(views.userListToolbar) - + if(args.showToolbar) { + views.userListTitle.text = args.title + vectorBaseActivity.setSupportActionBar(views.userListToolbar) + setupCloseView() + views.userListToolbar.isVisible = true + }else{ + views.userListToolbar.isVisible = false + } setupRecyclerView() setupSearchView() - setupCloseView() homeServerCapabilitiesViewModel.subscribe { views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } - viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) { + viewModel.selectSubscribe(this, UserListViewState::pendingSelections) { renderSelectedUsers(it) } @@ -105,7 +109,7 @@ class UserListFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { - val showMenuItem = it.pendingInvitees.isNotEmpty() + val showMenuItem = it.pendingSelections.isNotEmpty() menu.forEach { menuItem -> menuItem.isVisible = showMenuItem } @@ -114,7 +118,7 @@ class UserListFragment @Inject constructor( } override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { - sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees)) + sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingSelections)) return@withState true } @@ -156,14 +160,14 @@ class UserListFragment @Inject constructor( userListController.setData(it) } - private fun renderSelectedUsers(invitees: Set) { + private fun renderSelectedUsers(selections: Set) { invalidateOptionsMenu() val currentNumberOfChips = views.chipGroup.childCount - val newNumberOfChips = invitees.size + val newNumberOfChips = selections.size views.chipGroup.removeAllViews() - invitees.forEach { addChipToGroup(it) } + selections.forEach { addChipToGroup(it) } // Scroll to the bottom when adding chips. When removing chips, do not scroll if (newNumberOfChips >= currentNumberOfChips) { @@ -173,20 +177,22 @@ class UserListFragment @Inject constructor( } } - private fun addChipToGroup(pendingInvitee: PendingInvitee) { + private fun addChipToGroup(pendingSelection: PendingSelection) { val chip = Chip(requireContext()) chip.setChipBackgroundColorResource(android.R.color.transparent) chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat() - chip.text = pendingInvitee.getBestName() + chip.text = pendingSelection.getBestName() chip.isClickable = true chip.isCheckable = false chip.isCloseIconVisible = true views.chipGroup.addView(chip) chip.setOnCloseIconClickListener { - viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee)) + viewModel.handle(UserListAction.RemovePendingSelection(pendingSelection)) } } + fun getCurrentState() = withState(viewModel){ it } + override fun onInviteFriendClick() { viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing) } @@ -197,17 +203,17 @@ class UserListFragment @Inject constructor( override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(user))) } override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId)))) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid))) } override fun onUseQRCode() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt index 02fd13b39b..dce1f46b2f 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt @@ -24,5 +24,7 @@ data class UserListFragmentArgs( val title: String, val menuResId: Int, val excludedUserIds: Set? = null, - val existingRoomId: String? = null + val singleSelection: Boolean = false, + val showInviteActions: Boolean = true, + val showToolbar: Boolean = true ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt index b2cdee3e63..fca771793b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorSharedAction sealed class UserListSharedAction : VectorSharedAction { object Close : UserListSharedAction() object GoBack : UserListSharedAction() - data class OnMenuItemSelected(val itemId: Int, val invitees: Set) : UserListSharedAction() + data class OnMenuItemSelected(val itemId: Int, val selections: Set) : UserListSharedAction() object OpenPhoneBook : UserListSharedAction() object AddByQrCode : UserListSharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index f8eabbaed0..322f5eef21 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -68,21 +68,15 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } init { - setState { - copy( - myUserId = session.myUserId, - existingRoomId = initialState.existingRoomId - ) - } observeUsers() } override fun handle(action: UserListAction) { when (action) { - is UserListAction.SearchUsers -> handleSearchUsers(action.value) - is UserListAction.ClearSearchUsers -> handleClearSearchUsers() - is UserListAction.SelectPendingInvitee -> handleSelectUser(action) - is UserListAction.RemovePendingInvitee -> handleRemoveSelectedUser(action) + is UserListAction.SearchUsers -> handleSearchUsers(action.value) + is UserListAction.ClearSearchUsers -> handleClearSearchUsers() + is UserListAction.AddPendingSelection -> handleSelectUser(action) + is UserListAction.RemovePendingSelection -> handleRemoveSelectedUser(action) UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink() }.exhaustive } @@ -168,13 +162,13 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User .disposeOnClear() } - private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state -> - val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state -> + val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection) + setState { copy(pendingSelections = selections) } } - private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state -> - val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) - setState { copy(pendingInvitees = selectedUsers) } + private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingSelection) = withState { state -> + val selections = state.pendingSelections.minus(action.pendingSelection) + setState { copy(pendingSelections = selections) } } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index 69135f912d..1c7ccc70dd 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -28,24 +28,28 @@ data class UserListViewState( val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, val filteredMappedContacts: List = emptyList(), - val pendingInvitees: Set = emptySet(), - val createAndInviteState: Async = Uninitialized, + val pendingSelections: Set = emptySet(), val searchTerm: String = "", val myUserId: String = "", - val existingRoomId: String? = null + val singleSelection: Boolean, + private val showInviteActions: Boolean ) : MvRxState { constructor(args: UserListFragmentArgs) : this( - existingRoomId = args.existingRoomId + singleSelection = args.singleSelection, + showInviteActions = args.showInviteActions ) fun getSelectedMatrixId(): List { - return pendingInvitees + return pendingSelections .mapNotNull { when (it) { - is PendingInvitee.UserPendingInvitee -> it.user.userId - is PendingInvitee.ThreePidPendingInvitee -> null + is PendingSelection.UserPendingSelection -> it.user.userId + is PendingSelection.ThreePidPendingSelection -> null } } } + + fun showInviteActions() = showInviteActions && pendingSelections.isEmpty() + } diff --git a/vector/src/main/res/drawable/ic_call_transfer.xml b/vector/src/main/res/drawable/ic_call_transfer.xml new file mode 100644 index 0000000000..b2c28d6ba0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_call_transfer.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/vector/src/main/res/layout/activity_call_transfer.xml b/vector/src/main/res/layout/activity_call_transfer.xml new file mode 100644 index 0000000000..b748591788 --- /dev/null +++ b/vector/src/main/res/layout/activity_call_transfer.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + +