mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-23 18:05:59 +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
|
||||
|
||||
// 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)) {
|
||||
minus(element)
|
||||
if (singleElement) {
|
||||
emptySet()
|
||||
} else {
|
||||
minus(element)
|
||||
}
|
||||
} else {
|
||||
plus(element)
|
||||
if (singleElement) {
|
||||
setOf(element)
|
||||
} else {
|
||||
plus(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<VB: ViewBinding> : AppCompatActivity(), HasScreenInjector {
|
||||
|
@ -113,6 +115,19 @@ abstract class VectorBaseActivity<VB: ViewBinding> : AppCompatActivity(), HasScr
|
|||
.disposeOnDestroy()
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Views
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||
clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { onClicked() }
|
||||
.disposeOnDestroy()
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================================
|
||||
* DATA
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -63,6 +63,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
|
|||
dismiss()
|
||||
}
|
||||
|
||||
views.callControlsTransfer.views.itemVerificationClickableZone.debouncedClicks {
|
||||
callViewModel.handle(VectorCallViewActions.InitiateCallTransfer)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
callViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is VectorCallViewEvents.ShowSoundDeviceChooser -> {
|
||||
|
@ -153,5 +158,6 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
|
|||
views.callControlsToggleHoldResume.subTitle = null
|
||||
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
|
||||
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<ActivityCallBinding>(), CallContro
|
|||
views.callConnectingProgress.isVisible = true
|
||||
}
|
||||
}
|
||||
is CallState.Terminated -> {
|
||||
is CallState.Terminated -> {
|
||||
finish()
|
||||
}
|
||||
null -> {
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -320,6 +320,9 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||
onErrorTimoutConnect(event.turn)
|
||||
}
|
||||
is VectorCallViewEvents.ShowCallTransferScreen -> {
|
||||
navigator.openCallTransfer(this, callArgs.callId)
|
||||
}
|
||||
null -> {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,5 @@ sealed class VectorCallViewActions : VectorViewModelAction {
|
|||
object HeadSetButtonPressed : VectorCallViewActions()
|
||||
object ToggleCamera : VectorCallViewActions()
|
||||
object ToggleHDSD : VectorCallViewActions()
|
||||
object InitiateCallTransfer : VectorCallViewActions()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ sealed class VectorCallViewEvents : VectorViewEvents {
|
|||
val available: List<CallAudioManager.SoundDevice>,
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ data class VectorCallViewState(
|
|||
val callState: Async<CallState> = Uninitialized,
|
||||
val otherKnownCallInfo: CallInfo? = null,
|
||||
val callInfo: CallInfo = CallInfo(callId),
|
||||
val formattedDuration: String = ""
|
||||
val formattedDuration: String = "",
|
||||
val canOpponentBeTransferred: Boolean = false
|
||||
) : MvRxState {
|
||||
|
||||
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.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<ActivityCallTransferBinding>(), 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))
|
||||
|
|
|
@ -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
|
||||
|
||||
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<CallTransferViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
|
||||
private val callManager: WebRtcCallManager)
|
||||
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PendingInvitee>,
|
||||
val selections: Set<PendingSelection>,
|
||||
val existingDmRoomId: String?
|
||||
) : CreateDirectRoomAction()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PendingInvitee>) {
|
||||
private fun createRoomAndInviteSelectedUsers(selections: Set<PendingSelection>) {
|
||||
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()
|
||||
|
|
|
@ -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<PendingInvitee>) : InviteUsersToRoomAction()
|
||||
data class InviteSelectedUsers(val selections: Set<PendingSelection>) : InviteUsersToRoomAction()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<PendingInvitee>) {
|
||||
private fun inviteUsersToRoom(selections: Set<PendingSelection>) {
|
||||
_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))
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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<PendingInvitee>) {
|
||||
private fun renderSelectedUsers(selections: Set<PendingSelection>) {
|
||||
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() {
|
||||
|
|
|
@ -24,5 +24,7 @@ data class UserListFragmentArgs(
|
|||
val title: String,
|
||||
val menuResId: Int,
|
||||
val excludedUserIds: Set<String>? = null,
|
||||
val existingRoomId: String? = null
|
||||
val singleSelection: Boolean = false,
|
||||
val showInviteActions: Boolean = true,
|
||||
val showToolbar: Boolean = true
|
||||
) : Parcelable
|
||||
|
|
|
@ -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<PendingInvitee>) : UserListSharedAction()
|
||||
data class OnMenuItemSelected(val itemId: Int, val selections: Set<PendingSelection>) : UserListSharedAction()
|
||||
object OpenPhoneBook : UserListSharedAction()
|
||||
object AddByQrCode : UserListSharedAction()
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,24 +28,28 @@ data class UserListViewState(
|
|||
val knownUsers: Async<PagedList<User>> = Uninitialized,
|
||||
val directoryUsers: Async<List<User>> = Uninitialized,
|
||||
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
||||
val pendingInvitees: Set<PendingInvitee> = emptySet(),
|
||||
val createAndInviteState: Async<String> = Uninitialized,
|
||||
val pendingSelections: Set<PendingSelection> = 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<String> {
|
||||
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()
|
||||
|
||||
}
|
||||
|
|
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"
|
||||
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>
|
||||
|
|
|
@ -2782,5 +2782,9 @@
|
|||
<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_transfer_consult_first">Consult first</string>
|
||||
<string name="call_transfer_connect_action">Connect</string>
|
||||
<string name="call_transfer_title">Transfer</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue