VoIP : branch dialpad in transfer call screen

This commit is contained in:
ganfra 2021-01-19 10:46:43 +01:00
parent 55fd983fd3
commit 0f77c5be90
11 changed files with 168 additions and 83 deletions

View file

@ -29,4 +29,4 @@ data class CallCapabilities(
@Json(name = "m.call.transferee") val transferee: Boolean? = null
)
fun CallCapabilities?.supportCallTransfer() = this?.transferee.orFalse()
fun CallCapabilities?.supportCallTransfer() = true//this?.transferee.orFalse()

View file

@ -20,10 +20,11 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
class DialPadLookup(val session: Session,
val directRoomHelper: DirectRoomHelper,
val callManager: WebRtcCallManager
class DialPadLookup @Inject constructor(val session: Session,
val directRoomHelper: DirectRoomHelper,
val callManager: WebRtcCallManager
) {
class Failure : Throwable()

View file

@ -19,5 +19,6 @@ 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()
data class ConnectWithUserId(val consultFirst: Boolean, val selectedUserId: String) : CallTransferAction()
data class ConnectWithPhoneNumber(val consultFirst: Boolean, val phoneNumber: String) : CallTransferAction()
}

View file

@ -20,26 +20,17 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import com.google.android.material.tabs.TabLayoutMediator
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.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
import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs
import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel
import im.vector.app.features.userdirectory.UserListViewState
@ -56,16 +47,19 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>(),
UserListViewModel.Factory,
ContactsBookViewModel.Factory {
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
@Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
@Inject lateinit var callTransferViewModelFactory: CallTransferViewModel.Factory
@Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
@Inject lateinit var errorFormatter: ErrorFormatter
private lateinit var sectionsPagerAdapter: CallTransferPagerAdapter
private val callTransferViewModel: CallTransferViewModel by viewModel()
override fun getBinding() = ActivityCallTransferBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.vectorCoordinatorLayout
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
@ -86,39 +80,28 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
waitingView = views.waitingView.waitingView
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
sharedActionViewModel
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
UserListSharedAction.OpenPhoneBook -> openPhoneBook()
// not exhaustive because it's a sharedAction
else -> {
}
}
}
.disposeOnDestroy()
if (isFirstCreation()) {
addFragment(
R.id.callTransferFragmentContainer,
UserListFragment::class.java,
UserListFragmentArgs(
title = "",
menuResId = -1,
singleSelection = true,
showInviteActions = false,
showToolbar = false
),
USER_LIST_FRAGMENT_TAG
)
}
callTransferViewModel.observeViewEvents {
when (it) {
is CallTransferViewEvents.Dismiss -> finish()
CallTransferViewEvents.Loading -> showWaitingView()
CallTransferViewEvents.Loading -> showWaitingView()
is CallTransferViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure))
}
}
sectionsPagerAdapter = CallTransferPagerAdapter(this).register()
views.callTransferViewPager.adapter = sectionsPagerAdapter
sectionsPagerAdapter.onDialPadOkClicked = { phoneNumber ->
val action = CallTransferAction.ConnectWithPhoneNumber(views.callTransferConsultCheckBox.isChecked, phoneNumber)
callTransferViewModel.handle(action)
}
TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position ->
when (position) {
0 -> tab.text = getString(R.string.call_transfer_users_tab_title)
1 -> tab.text = getString(R.string.call_dial_pad_title)
}
}.attach()
configureToolbar(views.callTransferToolbar)
views.callTransferToolbar.title = getString(R.string.call_transfer_title)
setupConnectAction()
@ -126,36 +109,14 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>(),
private fun setupConnectAction() {
views.callTransferConnectAction.debouncedClicks {
val userListFragment = supportFragmentManager.findFragmentByTag(USER_LIST_FRAGMENT_TAG) as? UserListFragment
val selectedUser = userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull()
val selectedUser = sectionsPagerAdapter.userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull()
if (selectedUser != null) {
val action = CallTransferAction.Connect(views.callTransferConsultCheckBox.isChecked, selectedUser)
val action = CallTransferAction.ConnectWithUserId(views.callTransferConsultCheckBox.isChecked, selectedUser)
callTransferViewModel.handle(action)
}
}
}
private fun openPhoneBook() {
// Check permission first
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
this,
PERMISSION_REQUEST_CODE_READ_CONTACTS,
0)) {
addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allGranted(grantResults)) {
if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
doOnPostResume { addFragmentToBackstack(R.id.callTransferFragmentContainer, ContactsBookFragment::class.java) }
}
} else {
Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
}
}
companion object {
fun newIntent(context: Context, callId: String): Intent {

View file

@ -0,0 +1,90 @@
/*
* 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 android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.Restorable
import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.userdirectory.UserListFragment
import im.vector.app.features.userdirectory.UserListFragmentArgs
class CallTransferPagerAdapter(
private val fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity), Restorable{
val userListFragment: UserListFragment?
get() = findFragmentAtPosition(0) as? UserListFragment
val dialPadFragment: DialPadFragment?
get() = findFragmentAtPosition(1) as? DialPadFragment
var onDialPadOkClicked: ((String) -> Unit)? = null
override fun getItemCount() = 2
override fun createFragment(position: Int): Fragment {
val fragment: Fragment
if (position == 0) {
fragment = fragmentActivity.supportFragmentManager.fragmentFactory.instantiate(fragmentActivity.classLoader, UserListFragment::class.java.name)
fragment.arguments = UserListFragmentArgs(
title = "",
menuResId = -1,
singleSelection = true,
showInviteActions = false,
showToolbar = false,
showContactBookAction = false
).toMvRxBundle()
} else {
fragment = fragmentActivity.supportFragmentManager.fragmentFactory.instantiate(fragmentActivity.classLoader, DialPadFragment::class.java.name)
(fragment as DialPadFragment).apply {
arguments = Bundle().apply {
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true)
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
}
applyCallback()
}
}
return fragment
}
private fun findFragmentAtPosition(position: Int): Fragment? {
return fragmentActivity.supportFragmentManager.findFragmentByTag("f$position")
}
override fun onSaveInstanceState(outState: Bundle) = Unit
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
dialPadFragment?.applyCallback()
}
private fun DialPadFragment.applyCallback(): DialPadFragment{
callback = object : DialPadFragment.Callback {
override fun onOkClicked(formatted: String?, raw: String?) {
if (raw.isNullOrEmpty()) return
onDialPadOkClicked?.invoke(raw)
}
}
return this
}
}

View file

@ -24,6 +24,7 @@ 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.call.dialpad.DialPadLookup
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import kotlinx.coroutines.launch
@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
private val dialPadLookup: DialPadLookup,
callManager: WebRtcCallManager)
: VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) {
@ -72,11 +74,12 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
override fun handle(action: CallTransferAction) {
when (action) {
is CallTransferAction.Connect -> transferCall(action)
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action)
}.exhaustive
}
private fun transferCall(action: CallTransferAction.Connect) {
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
viewModelScope.launch {
try {
_viewEvents.post(CallTransferViewEvents.Loading)
@ -87,4 +90,18 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
}
}
}
private fun connectWithPhoneNumber(action: CallTransferAction.ConnectWithPhoneNumber) {
viewModelScope.launch {
try {
_viewEvents.post(CallTransferViewEvents.Loading)
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
call?.mxCall?.transfer(result.userId, result.roomId)
_viewEvents.post(CallTransferViewEvents.Dismiss)
} catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
}
}
}
}

View file

@ -64,13 +64,15 @@ class UserListController @Inject constructor(private val session: Session,
})
}
}
actionItem {
id(R.drawable.ic_baseline_perm_contact_calendar_24)
title(stringProvider.getString(R.string.contacts_book_title))
actionIconRes(R.drawable.ic_baseline_perm_contact_calendar_24)
clickAction(View.OnClickListener {
callback?.onContactBookClick()
})
if(currentState.showContactBookAction) {
actionItem {
id(R.drawable.ic_baseline_perm_contact_calendar_24)
title(stringProvider.getString(R.string.contacts_book_title))
actionIconRes(R.drawable.ic_baseline_perm_contact_calendar_24)
clickAction(View.OnClickListener {
callback?.onContactBookClick()
})
}
}
if (currentState.showInviteActions()) {
actionItem {

View file

@ -26,5 +26,6 @@ data class UserListFragmentArgs(
val excludedUserIds: Set<String>? = null,
val singleSelection: Boolean = false,
val showInviteActions: Boolean = true,
val showContactBookAction: Boolean = true,
val showToolbar: Boolean = true
) : Parcelable

View file

@ -31,13 +31,15 @@ data class UserListViewState(
val pendingSelections: Set<PendingSelection> = emptySet(),
val searchTerm: String = "",
val singleSelection: Boolean,
private val showInviteActions: Boolean
private val showInviteActions: Boolean,
val showContactBookAction: Boolean
) : MvRxState {
constructor(args: UserListFragmentArgs) : this(
excludedUserIds = args.excludedUserIds,
singleSelection = args.singleSelection,
showInviteActions = args.showInviteActions
showInviteActions = args.showInviteActions,
showContactBookAction = args.showContactBookAction
)
fun getSelectedMatrixId(): List<String> {

View file

@ -17,14 +17,24 @@
android:elevation="4dp"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/callTransferFragmentContainer"
<com.google.android.material.tabs.TabLayout
android:id="@+id/callTransferTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/callTransferToolbar"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/callTransferViewPager"
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" />
app:layout_constraintTop_toBottomOf="@id/callTransferTabLayout"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<RelativeLayout
android:background="?riotx_header_panel_background"
@ -67,7 +77,6 @@
</RelativeLayout>
<include
android:id="@+id/waiting_view"
layout="@layout/merge_overlay_waiting_view" />

View file

@ -2795,6 +2795,7 @@
<string name="call_transfer_connect_action">Connect</string>
<string name="call_transfer_title">Transfer</string>
<string name="call_transfer_failure">An error occurred while transferring call</string>
<string name="call_transfer_users_tab_title">Users</string>
</resources>