diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt index d911ca3b88..50fc9361e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallCapabilities.kt @@ -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() diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt index 89f7964cf2..79c770da28 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt @@ -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() diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt index cbdd9c252b..bd694ad14e 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt index 89a7caa764..53fd828a7e 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -20,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(), 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(), 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(), 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, 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 { diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt new file mode 100644 index 0000000000..2fbf7e6caa --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferPagerAdapter.kt @@ -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 + } + + +} diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index 0b83a6bcda..b4d6734817 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -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(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) + } + } + } + } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 331329ae61..bf9e6fd196 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -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 { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt index dce1f46b2f..795d45272c 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt @@ -26,5 +26,6 @@ data class UserListFragmentArgs( val excludedUserIds: Set? = null, val singleSelection: Boolean = false, val showInviteActions: Boolean = true, + val showContactBookAction: Boolean = true, val showToolbar: Boolean = true ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index 60ba3a17da..f1cbbd3b9d 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -31,13 +31,15 @@ data class UserListViewState( val pendingSelections: Set = 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 { diff --git a/vector/src/main/res/layout/activity_call_transfer.xml b/vector/src/main/res/layout/activity_call_transfer.xml index e4e50b2ce2..64ddd29319 100644 --- a/vector/src/main/res/layout/activity_call_transfer.xml +++ b/vector/src/main/res/layout/activity_call_transfer.xml @@ -17,14 +17,24 @@ android:elevation="4dp" app:layout_constraintTop_toTopOf="parent" /> - + + + app:layout_constraintTop_toBottomOf="@id/callTransferTabLayout" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> - diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7c8b82fe51..4ce4bd2adf 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2795,6 +2795,7 @@ Connect Transfer An error occurred while transferring call + Users