mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-17 12:30:07 +03:00
VoIP: use UserListFragment to select someone for call transfer (+ clean some code)
This commit is contained in:
parent
22c981d8bf
commit
439ea42b54
32 changed files with 376 additions and 123 deletions
|
@ -17,10 +17,18 @@
|
||||||
package im.vector.app.core.extensions
|
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
|
// Create a new Set including the provided element if not already present, or removing the element if already present
|
||||||
fun <T> Set<T>.toggle(element: T): Set<T> {
|
fun <T> Set<T>.toggle(element: T, singleElement: Boolean = false): Set<T> {
|
||||||
return if (contains(element)) {
|
return if (contains(element)) {
|
||||||
minus(element)
|
if (singleElement) {
|
||||||
|
emptySet()
|
||||||
|
} else {
|
||||||
|
minus(element)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
plus(element)
|
if (singleElement) {
|
||||||
|
setOf(element)
|
||||||
|
} else {
|
||||||
|
plus(element)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.bumptech.glide.util.Util
|
import com.bumptech.glide.util.Util
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.jakewharton.rxbinding3.view.clicks
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
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.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.failure.GlobalError
|
import org.matrix.android.sdk.api.failure.GlobalError
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
||||||
|
@ -113,6 +115,19 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Views
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||||
|
clicks()
|
||||||
|
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onClicked() }
|
||||||
|
.disposeOnDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* DATA
|
* DATA
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
|
@ -63,6 +63,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.callControlsTransfer.views.itemVerificationClickableZone.debouncedClicks {
|
||||||
|
callViewModel.handle(VectorCallViewActions.InitiateCallTransfer)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
callViewModel.observeViewEvents {
|
callViewModel.observeViewEvents {
|
||||||
when (it) {
|
when (it) {
|
||||||
is VectorCallViewEvents.ShowSoundDeviceChooser -> {
|
is VectorCallViewEvents.ShowSoundDeviceChooser -> {
|
||||||
|
@ -153,5 +158,6 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
|
||||||
views.callControlsToggleHoldResume.subTitle = null
|
views.callControlsToggleHoldResume.subTitle = null
|
||||||
views.callControlsToggleHoldResume.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_call_hold_action)
|
views.callControlsToggleHoldResume.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_call_hold_action)
|
||||||
}
|
}
|
||||||
|
views.callControlsTransfer.isVisible = state.canOpponentBeTransferred
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
views.callConnectingProgress.isVisible = true
|
views.callConnectingProgress.isVisible = true
|
||||||
configureCallInfo(state)
|
configureCallInfo(state)
|
||||||
}
|
}
|
||||||
is CallState.Connected -> {
|
is CallState.Connected -> {
|
||||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||||
views.smallIsHeldIcon.isVisible = true
|
views.smallIsHeldIcon.isVisible = true
|
||||||
|
@ -227,10 +227,10 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
views.callConnectingProgress.isVisible = true
|
views.callConnectingProgress.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CallState.Terminated -> {
|
is CallState.Terminated -> {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -320,6 +320,9 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||||
onErrorTimoutConnect(event.turn)
|
onErrorTimoutConnect(event.turn)
|
||||||
}
|
}
|
||||||
|
is VectorCallViewEvents.ShowCallTransferScreen -> {
|
||||||
|
navigator.openCallTransfer(this, callArgs.callId)
|
||||||
|
}
|
||||||
null -> {
|
null -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,5 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
||||||
object HeadSetButtonPressed : VectorCallViewActions()
|
object HeadSetButtonPressed : VectorCallViewActions()
|
||||||
object ToggleCamera : VectorCallViewActions()
|
object ToggleCamera : VectorCallViewActions()
|
||||||
object ToggleHDSD : VectorCallViewActions()
|
object ToggleHDSD : VectorCallViewActions()
|
||||||
|
object InitiateCallTransfer : VectorCallViewActions()
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ sealed class VectorCallViewEvents : VectorViewEvents {
|
||||||
val available: List<CallAudioManager.SoundDevice>,
|
val available: List<CallAudioManager.SoundDevice>,
|
||||||
val current: CallAudioManager.SoundDevice
|
val current: CallAudioManager.SoundDevice
|
||||||
) : VectorCallViewEvents()
|
) : VectorCallViewEvents()
|
||||||
|
object ShowCallTransferScreen: VectorCallViewEvents()
|
||||||
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
|
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
|
||||||
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
|
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
|
||||||
// object CallAccepted : VectorCallViewEvents()
|
// object CallAccepted : VectorCallViewEvents()
|
||||||
|
|
|
@ -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.MxCall
|
||||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
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.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.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
|
@ -114,7 +115,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
callState = Success(callState)
|
callState = Success(callState),
|
||||||
|
canOpponentBeTransferred = call.capabilities.supportCallTransfer()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +190,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT,
|
isFrontCamera = webRtcCall.currentCameraType() == CameraType.FRONT,
|
||||||
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
canSwitchCamera = webRtcCall.canSwitchCamera(),
|
||||||
formattedDuration = webRtcCall.formattedDuration(),
|
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)
|
updateOtherKnownCall(webRtcCall)
|
||||||
|
@ -269,6 +272,11 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
if (!state.isVideoCall) return@withState
|
if (!state.isVideoCall) return@withState
|
||||||
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
|
call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD)
|
||||||
}
|
}
|
||||||
|
VectorCallViewActions.InitiateCallTransfer -> {
|
||||||
|
_viewEvents.post(
|
||||||
|
VectorCallViewEvents.ShowCallTransferScreen
|
||||||
|
)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,8 @@ data class VectorCallViewState(
|
||||||
val callState: Async<CallState> = Uninitialized,
|
val callState: Async<CallState> = Uninitialized,
|
||||||
val otherKnownCallInfo: CallInfo? = null,
|
val otherKnownCallInfo: CallInfo? = null,
|
||||||
val callInfo: CallInfo = CallInfo(callId),
|
val callInfo: CallInfo = CallInfo(callId),
|
||||||
val formattedDuration: String = ""
|
val formattedDuration: String = "",
|
||||||
|
val canOpponentBeTransferred: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
data class CallInfo(
|
data class CallInfo(
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -20,19 +20,20 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
|
import com.airbnb.mvrx.viewModel
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ScreenComponent
|
import im.vector.app.core.di.ScreenComponent
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.extensions.addFragment
|
import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
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.PERMISSIONS_FOR_MEMBERS_SEARCH
|
||||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
|
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
|
||||||
import im.vector.app.core.utils.allGranted
|
import im.vector.app.core.utils.allGranted
|
||||||
import im.vector.app.core.utils.checkPermissions
|
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.ContactsBookFragment
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewState
|
import im.vector.app.features.contactsbook.ContactsBookViewState
|
||||||
|
@ -48,7 +49,9 @@ import javax.inject.Inject
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class CallTransferArgs(val callId: String) : Parcelable
|
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<ActivityCallTransferBinding>(), CallTransferViewModel.Factory, UserListViewModel.Factory, ContactsBookViewModel.Factory {
|
||||||
|
|
||||||
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
|
||||||
@Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
|
@Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
|
||||||
|
@ -56,6 +59,10 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac
|
||||||
@Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
|
@Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
|
||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
private val callTransferViewModel: CallTransferViewModel by viewModel()
|
||||||
|
|
||||||
|
override fun getBinding() = ActivityCallTransferBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
override fun injectWith(injector: ScreenComponent) {
|
override fun injectWith(injector: ScreenComponent) {
|
||||||
super.injectWith(injector)
|
super.injectWith(injector)
|
||||||
injector.inject(this)
|
injector.inject(this)
|
||||||
|
@ -75,33 +82,50 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
views.toolbar.visibility = View.GONE
|
|
||||||
|
|
||||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||||
sharedActionViewModel
|
sharedActionViewModel
|
||||||
.observe()
|
.observe()
|
||||||
.subscribe { sharedAction ->
|
.subscribe { sharedAction ->
|
||||||
when (sharedAction) {
|
when (sharedAction) {
|
||||||
UserListSharedAction.Close -> finish()
|
UserListSharedAction.OpenPhoneBook -> openPhoneBook()
|
||||||
UserListSharedAction.GoBack -> onBackPressed()
|
|
||||||
UserListSharedAction.OpenPhoneBook -> openPhoneBook()
|
|
||||||
// not exhaustive because it's a sharedAction
|
// not exhaustive because it's a sharedAction
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
addFragment(
|
addFragment(
|
||||||
R.id.container,
|
R.id.callTransferFragmentContainer,
|
||||||
UserListFragment::class.java,
|
UserListFragment::class.java,
|
||||||
UserListFragmentArgs(
|
UserListFragmentArgs(
|
||||||
title = "Call transfer",
|
title = "",
|
||||||
menuResId = -1
|
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() {
|
private fun openPhoneBook() {
|
||||||
|
@ -110,7 +134,7 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac
|
||||||
this,
|
this,
|
||||||
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
PERMISSION_REQUEST_CODE_READ_CONTACTS,
|
||||||
0)) {
|
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)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
|
||||||
doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
|
doOnPostResume { addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
|
||||||
|
@ -126,7 +150,7 @@ class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Fac
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newIntent(context: Context, callId: String): Intent {
|
fun newIntent(context: Context, callId: String): Intent {
|
||||||
return Intent(context, CallTransferActivity::class.java).also {
|
return Intent(context, CallTransferActivity::class.java).also {
|
||||||
it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId))
|
it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId))
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -16,20 +16,23 @@
|
||||||
|
|
||||||
package im.vector.app.features.call.transfer
|
package im.vector.app.features.call.transfer
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
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.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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
|
import timber.log.Timber
|
||||||
|
|
||||||
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState)
|
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
||||||
: VectorViewModel<CallTransferViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
private val callManager: WebRtcCallManager)
|
||||||
|
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentContactsBookBinding
|
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.UserListAction
|
||||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||||
|
@ -132,13 +132,13 @@ class ContactsBookFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onMatrixIdClick(matrixId: String) {
|
override fun onMatrixIdClick(matrixId: String) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
|
viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId))))
|
||||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onThreePidClick(threePid: ThreePid) {
|
override fun onThreePidClick(threePid: ThreePid) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
|
viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid)))
|
||||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
package im.vector.app.features.createdirect
|
package im.vector.app.features.createdirect
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
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 {
|
sealed class CreateDirectRoomAction : VectorViewModelAction {
|
||||||
data class CreateRoomAndInviteSelectedUsers(
|
data class CreateRoomAndInviteSelectedUsers(
|
||||||
val invitees: Set<PendingInvitee>,
|
val selections: Set<PendingSelection>,
|
||||||
val existingDmRoomId: String?
|
val existingDmRoomId: String?
|
||||||
) : CreateDirectRoomAction()
|
) : CreateDirectRoomAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
|
||||||
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
||||||
if (action.itemId == R.id.action_create_direct_room) {
|
if (action.itemId == R.id.action_create_direct_room) {
|
||||||
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(
|
viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(
|
||||||
action.invitees,
|
action.selections,
|
||||||
null
|
null
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.checkPermissions
|
||||||
import im.vector.app.core.utils.registerForPermissionsResult
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
import im.vector.app.databinding.FragmentQrCodeScannerBinding
|
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 me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
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)
|
val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
|
||||||
|
|
||||||
viewModel.handle(
|
viewModel.handle(
|
||||||
CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm)
|
CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingSelection.UserPendingSelection(qrInvitee)), existingDm)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,10 +26,9 @@ import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
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.getElementWellknown
|
||||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
|
@ -77,11 +76,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create the DM
|
// Create the DM
|
||||||
createRoomAndInviteSelectedUsers(action.invitees)
|
createRoomAndInviteSelectedUsers(action.selections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRoomAndInviteSelectedUsers(invitees: Set<PendingInvitee>) {
|
private fun createRoomAndInviteSelectedUsers(selections: Set<PendingSelection>) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
|
val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
|
||||||
?.isE2EByDefault()
|
?.isE2EByDefault()
|
||||||
|
@ -89,10 +88,10 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
val roomParams = CreateRoomParams()
|
val roomParams = CreateRoomParams()
|
||||||
.apply {
|
.apply {
|
||||||
invitees.forEach {
|
selections.forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
|
is PendingSelection.UserPendingSelection -> invitedUserIds.add(it.user.userId)
|
||||||
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
|
is PendingSelection.ThreePidPendingSelection -> invite3pids.add(it.threePid)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
setDirectMessage()
|
setDirectMessage()
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
package im.vector.app.features.invite
|
package im.vector.app.features.invite
|
||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
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 {
|
sealed class InviteUsersToRoomAction : VectorViewModelAction {
|
||||||
data class InviteSelectedUsers(val invitees: Set<PendingInvitee>) : InviteUsersToRoomAction()
|
data class InviteSelectedUsers(val selections: Set<PendingSelection>) : InviteUsersToRoomAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,6 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
|
||||||
}
|
}
|
||||||
.disposeOnDestroy()
|
.disposeOnDestroy()
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG)
|
|
||||||
addFragment(
|
addFragment(
|
||||||
R.id.container,
|
R.id.container,
|
||||||
UserListFragment::class.java,
|
UserListFragment::class.java,
|
||||||
|
@ -103,7 +102,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
|
||||||
title = getString(R.string.invite_users_to_room_title),
|
title = getString(R.string.invite_users_to_room_title),
|
||||||
menuResId = R.menu.vector_invite_users_to_room,
|
menuResId = R.menu.vector_invite_users_to_room,
|
||||||
excludedUserIds = viewModel.getUserIdsOfRoomMembers(),
|
excludedUserIds = viewModel.getUserIdsOfRoomMembers(),
|
||||||
existingRoomId = args?.roomId
|
showInviteActions = false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -113,7 +112,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
|
||||||
|
|
||||||
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
|
||||||
if (action.itemId == R.id.action_invite_users_to_room_invite) {
|
if (action.itemId == R.id.action_invite_users_to_room_invite) {
|
||||||
viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees))
|
viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selections))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,7 @@ import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.contactsbook.ContactsBookViewModel
|
import im.vector.app.features.userdirectory.PendingSelection
|
||||||
import im.vector.app.features.userdirectory.PendingInvitee
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
|
@ -58,30 +57,30 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
|
|
||||||
override fun handle(action: InviteUsersToRoomAction) {
|
override fun handle(action: InviteUsersToRoomAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.invitees)
|
is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inviteUsersToRoom(invitees: Set<PendingInvitee>) {
|
private fun inviteUsersToRoom(selections: Set<PendingSelection>) {
|
||||||
_viewEvents.post(InviteUsersToRoomViewEvents.Loading)
|
_viewEvents.post(InviteUsersToRoomViewEvents.Loading)
|
||||||
|
|
||||||
Observable.fromIterable(invitees).flatMapCompletable { user ->
|
Observable.fromIterable(selections).flatMapCompletable { user ->
|
||||||
when (user) {
|
when (user) {
|
||||||
is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null)
|
is PendingSelection.UserPendingSelection -> room.rx().invite(user.user.userId, null)
|
||||||
is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid)
|
is PendingSelection.ThreePidPendingSelection -> room.rx().invite3pid(user.threePid)
|
||||||
}
|
}
|
||||||
}.subscribe(
|
}.subscribe(
|
||||||
{
|
{
|
||||||
val successMessage = when (invitees.size) {
|
val successMessage = when (selections.size) {
|
||||||
1 -> stringProvider.getString(R.string.invitation_sent_to_one_user,
|
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,
|
2 -> stringProvider.getString(R.string.invitations_sent_to_two_users,
|
||||||
invitees.first().getBestName(),
|
selections.first().getBestName(),
|
||||||
invitees.last().getBestName())
|
selections.last().getBestName())
|
||||||
else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users,
|
else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users,
|
||||||
invitees.size - 1,
|
selections.size - 1,
|
||||||
invitees.first().getBestName(),
|
selections.first().getBestName(),
|
||||||
invitees.size - 1)
|
selections.size - 1)
|
||||||
}
|
}
|
||||||
_viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
|
_viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
|
||||||
},
|
},
|
||||||
|
|
|
@ -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.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
import org.matrix.android.sdk.api.session.user.model.User
|
||||||
|
|
||||||
sealed class PendingInvitee {
|
sealed class PendingSelection {
|
||||||
data class UserPendingInvitee(val user: User) : PendingInvitee()
|
data class UserPendingSelection(val user: User) : PendingSelection()
|
||||||
data class ThreePidPendingInvitee(val threePid: ThreePid) : PendingInvitee()
|
data class ThreePidPendingSelection(val threePid: ThreePid) : PendingSelection()
|
||||||
|
|
||||||
fun getBestName(): String {
|
fun getBestName(): String {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is UserPendingInvitee -> user.getBestName()
|
is UserPendingSelection -> user.getBestName()
|
||||||
is ThreePidPendingInvitee -> threePid.value
|
is ThreePidPendingSelection -> threePid.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
sealed class UserListAction : VectorViewModelAction {
|
sealed class UserListAction : VectorViewModelAction {
|
||||||
data class SearchUsers(val value: String) : UserListAction()
|
data class SearchUsers(val value: String) : UserListAction()
|
||||||
object ClearSearchUsers : UserListAction()
|
object ClearSearchUsers : UserListAction()
|
||||||
data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
|
data class AddPendingSelection(val pendingSelection: PendingSelection) : UserListAction()
|
||||||
data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
|
data class RemovePendingSelection(val pendingSelection: PendingSelection) : UserListAction()
|
||||||
object ComputeMatrixToLinkForSharing : UserListAction()
|
object ComputeMatrixToLinkForSharing : UserListAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,10 +54,7 @@ class UserListController @Inject constructor(private val session: Session,
|
||||||
|
|
||||||
// Build generic items
|
// Build generic items
|
||||||
if (currentState.searchTerm.isBlank()) {
|
if (currentState.searchTerm.isBlank()) {
|
||||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
if (currentState.showInviteActions()) {
|
||||||
if (currentState.pendingInvitees.isEmpty()
|
|
||||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
|
||||||
&& currentState.existingRoomId == null) {
|
|
||||||
actionItem {
|
actionItem {
|
||||||
id(R.drawable.ic_share)
|
id(R.drawable.ic_share)
|
||||||
title(stringProvider.getString(R.string.invite_friends))
|
title(stringProvider.getString(R.string.invite_friends))
|
||||||
|
@ -75,9 +72,7 @@ class UserListController @Inject constructor(private val session: Session,
|
||||||
callback?.onContactBookClick()
|
callback?.onContactBookClick()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (currentState.pendingInvitees.isEmpty()
|
if (currentState.showInviteActions()) {
|
||||||
// For now we remove this option if in invite to existing room flow (and not create DM)
|
|
||||||
&& currentState.existingRoomId == null) {
|
|
||||||
actionItem {
|
actionItem {
|
||||||
id(R.drawable.ic_qr_code_add)
|
id(R.drawable.ic_qr_code_add)
|
||||||
title(stringProvider.getString(R.string.qr_code))
|
title(stringProvider.getString(R.string.qr_code))
|
||||||
|
|
|
@ -67,18 +67,22 @@ class UserListFragment @Inject constructor(
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||||
views.userListTitle.text = args.title
|
if(args.showToolbar) {
|
||||||
vectorBaseActivity.setSupportActionBar(views.userListToolbar)
|
views.userListTitle.text = args.title
|
||||||
|
vectorBaseActivity.setSupportActionBar(views.userListToolbar)
|
||||||
|
setupCloseView()
|
||||||
|
views.userListToolbar.isVisible = true
|
||||||
|
}else{
|
||||||
|
views.userListToolbar.isVisible = false
|
||||||
|
}
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupSearchView()
|
setupSearchView()
|
||||||
setupCloseView()
|
|
||||||
|
|
||||||
homeServerCapabilitiesViewModel.subscribe {
|
homeServerCapabilitiesViewModel.subscribe {
|
||||||
views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) {
|
viewModel.selectSubscribe(this, UserListViewState::pendingSelections) {
|
||||||
renderSelectedUsers(it)
|
renderSelectedUsers(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +109,7 @@ class UserListFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
withState(viewModel) {
|
withState(viewModel) {
|
||||||
val showMenuItem = it.pendingInvitees.isNotEmpty()
|
val showMenuItem = it.pendingSelections.isNotEmpty()
|
||||||
menu.forEach { menuItem ->
|
menu.forEach { menuItem ->
|
||||||
menuItem.isVisible = showMenuItem
|
menuItem.isVisible = showMenuItem
|
||||||
}
|
}
|
||||||
|
@ -114,7 +118,7 @@ class UserListFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) {
|
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
|
return@withState true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,14 +160,14 @@ class UserListFragment @Inject constructor(
|
||||||
userListController.setData(it)
|
userListController.setData(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSelectedUsers(invitees: Set<PendingInvitee>) {
|
private fun renderSelectedUsers(selections: Set<PendingSelection>) {
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
val currentNumberOfChips = views.chipGroup.childCount
|
val currentNumberOfChips = views.chipGroup.childCount
|
||||||
val newNumberOfChips = invitees.size
|
val newNumberOfChips = selections.size
|
||||||
|
|
||||||
views.chipGroup.removeAllViews()
|
views.chipGroup.removeAllViews()
|
||||||
invitees.forEach { addChipToGroup(it) }
|
selections.forEach { addChipToGroup(it) }
|
||||||
|
|
||||||
// Scroll to the bottom when adding chips. When removing chips, do not scroll
|
// Scroll to the bottom when adding chips. When removing chips, do not scroll
|
||||||
if (newNumberOfChips >= currentNumberOfChips) {
|
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())
|
val chip = Chip(requireContext())
|
||||||
chip.setChipBackgroundColorResource(android.R.color.transparent)
|
chip.setChipBackgroundColorResource(android.R.color.transparent)
|
||||||
chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat()
|
chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat()
|
||||||
chip.text = pendingInvitee.getBestName()
|
chip.text = pendingSelection.getBestName()
|
||||||
chip.isClickable = true
|
chip.isClickable = true
|
||||||
chip.isCheckable = false
|
chip.isCheckable = false
|
||||||
chip.isCloseIconVisible = true
|
chip.isCloseIconVisible = true
|
||||||
views.chipGroup.addView(chip)
|
views.chipGroup.addView(chip)
|
||||||
chip.setOnCloseIconClickListener {
|
chip.setOnCloseIconClickListener {
|
||||||
viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee))
|
viewModel.handle(UserListAction.RemovePendingSelection(pendingSelection))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCurrentState() = withState(viewModel){ it }
|
||||||
|
|
||||||
override fun onInviteFriendClick() {
|
override fun onInviteFriendClick() {
|
||||||
viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing)
|
viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing)
|
||||||
}
|
}
|
||||||
|
@ -197,17 +203,17 @@ class UserListFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onItemClick(user: User) {
|
override fun onItemClick(user: User) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
|
viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMatrixIdClick(matrixId: String) {
|
override fun onMatrixIdClick(matrixId: String) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
|
viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.UserPendingSelection(User(matrixId))))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onThreePidClick(threePid: ThreePid) {
|
override fun onThreePidClick(threePid: ThreePid) {
|
||||||
view?.hideKeyboard()
|
view?.hideKeyboard()
|
||||||
viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
|
viewModel.handle(UserListAction.AddPendingSelection(PendingSelection.ThreePidPendingSelection(threePid)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUseQRCode() {
|
override fun onUseQRCode() {
|
||||||
|
|
|
@ -24,5 +24,7 @@ data class UserListFragmentArgs(
|
||||||
val title: String,
|
val title: String,
|
||||||
val menuResId: Int,
|
val menuResId: Int,
|
||||||
val excludedUserIds: Set<String>? = null,
|
val excludedUserIds: Set<String>? = null,
|
||||||
val existingRoomId: String? = null
|
val singleSelection: Boolean = false,
|
||||||
|
val showInviteActions: Boolean = true,
|
||||||
|
val showToolbar: Boolean = true
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorSharedAction
|
||||||
sealed class UserListSharedAction : VectorSharedAction {
|
sealed class UserListSharedAction : VectorSharedAction {
|
||||||
object Close : UserListSharedAction()
|
object Close : UserListSharedAction()
|
||||||
object GoBack : UserListSharedAction()
|
object GoBack : UserListSharedAction()
|
||||||
data class OnMenuItemSelected(val itemId: Int, val invitees: Set<PendingInvitee>) : UserListSharedAction()
|
data class OnMenuItemSelected(val itemId: Int, val selections: Set<PendingSelection>) : UserListSharedAction()
|
||||||
object OpenPhoneBook : UserListSharedAction()
|
object OpenPhoneBook : UserListSharedAction()
|
||||||
object AddByQrCode : UserListSharedAction()
|
object AddByQrCode : UserListSharedAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,21 +68,15 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setState {
|
|
||||||
copy(
|
|
||||||
myUserId = session.myUserId,
|
|
||||||
existingRoomId = initialState.existingRoomId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
observeUsers()
|
observeUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: UserListAction) {
|
override fun handle(action: UserListAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is UserListAction.SearchUsers -> handleSearchUsers(action.value)
|
is UserListAction.SearchUsers -> handleSearchUsers(action.value)
|
||||||
is UserListAction.ClearSearchUsers -> handleClearSearchUsers()
|
is UserListAction.ClearSearchUsers -> handleClearSearchUsers()
|
||||||
is UserListAction.SelectPendingInvitee -> handleSelectUser(action)
|
is UserListAction.AddPendingSelection -> handleSelectUser(action)
|
||||||
is UserListAction.RemovePendingInvitee -> handleRemoveSelectedUser(action)
|
is UserListAction.RemovePendingSelection -> handleRemoveSelectedUser(action)
|
||||||
UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink()
|
UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
@ -168,13 +162,13 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User
|
||||||
.disposeOnClear()
|
.disposeOnClear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state ->
|
private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state ->
|
||||||
val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee)
|
val selections = state.pendingSelections.toggle(action.pendingSelection, singleElement = state.singleSelection)
|
||||||
setState { copy(pendingInvitees = selectedUsers) }
|
setState { copy(pendingSelections = selections) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state ->
|
private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingSelection) = withState { state ->
|
||||||
val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee)
|
val selections = state.pendingSelections.minus(action.pendingSelection)
|
||||||
setState { copy(pendingInvitees = selectedUsers) }
|
setState { copy(pendingSelections = selections) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,24 +28,28 @@ data class UserListViewState(
|
||||||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||||
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
||||||
val pendingInvitees: Set<PendingInvitee> = emptySet(),
|
val pendingSelections: Set<PendingSelection> = emptySet(),
|
||||||
val createAndInviteState: Async<String> = Uninitialized,
|
|
||||||
val searchTerm: String = "",
|
val searchTerm: String = "",
|
||||||
val myUserId: String = "",
|
val myUserId: String = "",
|
||||||
val existingRoomId: String? = null
|
val singleSelection: Boolean,
|
||||||
|
private val showInviteActions: Boolean
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: UserListFragmentArgs) : this(
|
constructor(args: UserListFragmentArgs) : this(
|
||||||
existingRoomId = args.existingRoomId
|
singleSelection = args.singleSelection,
|
||||||
|
showInviteActions = args.showInviteActions
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getSelectedMatrixId(): List<String> {
|
fun getSelectedMatrixId(): List<String> {
|
||||||
return pendingInvitees
|
return pendingSelections
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
when (it) {
|
when (it) {
|
||||||
is PendingInvitee.UserPendingInvitee -> it.user.userId
|
is PendingSelection.UserPendingSelection -> it.user.userId
|
||||||
is PendingInvitee.ThreePidPendingInvitee -> null
|
is PendingSelection.ThreePidPendingSelection -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showInviteActions() = showInviteActions && pendingSelections.isEmpty()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
17
vector/src/main/res/drawable/ic_call_transfer.xml
Normal file
17
vector/src/main/res/drawable/ic_call_transfer.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,0h24v24h-24z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8.027,15.9613C9.168,17.1932 11.9148,19.3263 12.6635,19.7641C12.7078,19.79 12.7585,19.8201 12.8152,19.8538C13.9576,20.5329 17.5373,22.6609 20.1454,20.6694C22.1661,19.1266 21.5091,17.3909 20.8289,16.875C20.3633,16.5128 18.9914,15.5145 17.7006,14.6152C16.4331,13.7322 15.7268,14.4397 15.2492,14.918C15.2404,14.9268 15.2317,14.9355 15.2231,14.9442L14.2621,15.9051C14.0174,16.1498 13.6451,16.0605 13.2886,15.7804C12.0092,14.8061 11.0681,13.8659 10.5972,13.395L10.5933,13.391C10.1225,12.9202 9.1939,11.9908 8.2196,10.7114C7.9395,10.3548 7.8502,9.9826 8.0949,9.7379L9.0559,8.7769C9.0645,8.7683 9.0732,8.7596 9.082,8.7508C9.5603,8.2732 10.2678,7.5668 9.3848,6.2994C8.4855,5.0086 7.4872,3.6367 7.125,3.1711C6.6091,2.4909 4.8734,1.8339 3.3306,3.8546C1.3391,6.4627 3.4671,10.0424 4.1462,11.1848C4.1799,11.2415 4.2101,11.2922 4.2359,11.3365C4.6737,12.0851 6.7951,14.8203 8.027,15.9613Z"
|
||||||
|
android:fillColor="#C1C6CD"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.3084,9.7333C12.9179,10.1238 12.9179,10.757 13.3084,11.1475C13.6989,11.538 14.3321,11.538 14.7226,11.1475L20.4401,5.43L20.4401,9.9101C20.4401,10.4624 20.8878,10.9101 21.4401,10.9101C21.9924,10.9101 22.4401,10.4624 22.4401,9.9101L22.4401,3.0158C22.4401,2.4635 21.9924,2.0158 21.4401,2.0158H14.5458C13.9935,2.0158 13.5458,2.4635 13.5458,3.0158C13.5458,3.5681 13.9935,4.0158 14.5458,4.0158L19.0259,4.0158L13.3084,9.7333Z"
|
||||||
|
android:fillColor="#C1C6CD"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
73
vector/src/main/res/layout/activity_call_transfer.xml
Normal file
73
vector/src/main/res/layout/activity_call_transfer.xml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/vector_coordinator_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/callTransferToolbar"
|
||||||
|
style="@style/VectorToolbarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/callTransferFragmentContainer"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/callTransferActionsLayout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/callTransferToolbar" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:background="?riotx_header_panel_background"
|
||||||
|
android:id="@+id/callTransferActionsLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingVertical="8dp"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/callTransferConsultCheckBox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/callTransferConsultTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toEndOf="@id/callTransferConsultCheckBox"
|
||||||
|
android:layout_toStartOf="@+id/callTransferConnectAction"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="@string/call_transfer_consult_first" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/callTransferConnectAction"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:text="@string/call_transfer_connect_action"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<include layout="@layout/merge_overlay_waiting_view" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -44,4 +44,13 @@
|
||||||
app:tint="?attr/riotx_text_primary"
|
app:tint="?attr/riotx_text_primary"
|
||||||
tools:actionDescription="" />
|
tools:actionDescription="" />
|
||||||
|
|
||||||
|
<im.vector.app.core.ui.views.BottomSheetActionButton
|
||||||
|
android:id="@+id/callControlsTransfer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:actionTitle="@string/call_transfer_title"
|
||||||
|
app:leftIcon="@drawable/ic_call_transfer"
|
||||||
|
app:tint="?attr/riotx_text_primary"
|
||||||
|
tools:actionDescription="" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -2782,5 +2782,9 @@
|
||||||
<string name="call_active_and_multiple_paused">1 active call (%1$s) · %2$d paused calls</string>
|
<string name="call_active_and_multiple_paused">1 active call (%1$s) · %2$d paused calls</string>
|
||||||
<string name="call_only_multiple_paused">%1$d paused calls</string>
|
<string name="call_only_multiple_paused">%1$d paused calls</string>
|
||||||
|
|
||||||
|
<string name="call_transfer_consult_first">Consult first</string>
|
||||||
|
<string name="call_transfer_connect_action">Connect</string>
|
||||||
|
<string name="call_transfer_title">Transfer</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Reference in a new issue