VoIP: start adding UI for call transfer

This commit is contained in:
ganfra 2020-12-22 16:16:46 +01:00
parent 8797d7562d
commit 22c981d8bf
13 changed files with 263 additions and 24 deletions

View file

@ -243,6 +243,7 @@
<activity android:name=".features.pin.PinActivity" />
<activity android:name=".features.home.room.detail.search.SearchActivity" />
<activity android:name=".features.usercode.UserCodeActivity" />
<activity android:name=".features.call.transfer.CallTransferActivity" />
<!-- Services -->

View file

@ -28,6 +28,7 @@ import im.vector.app.features.MainActivity
import im.vector.app.features.call.CallControlsBottomSheet
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
@ -146,6 +147,7 @@ interface ScreenComponent {
fun inject(activity: VectorJitsiActivity)
fun inject(activity: SearchActivity)
fun inject(activity: UserCodeActivity)
fun inject(callTransferActivity: CallTransferActivity)
/* ==========================================================================================
* BottomSheets

View file

@ -0,0 +1,136 @@
/*
* 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.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 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.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.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
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@Parcelize
data class CallTransferArgs(val callId: String) : Parcelable
class CallTransferActivity : SimpleFragmentActivity(), CallTransferViewModel.Factory, 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
override fun injectWith(injector: ScreenComponent) {
super.injectWith(injector)
injector.inject(this)
}
override fun create(initialState: UserListViewState): UserListViewModel {
return userListViewModelFactory.create(initialState)
}
override fun create(initialState: CallTransferViewState): CallTransferViewModel {
return callTransferViewModelFactory.create(initialState)
}
override fun create(initialState: ContactsBookViewState): ContactsBookViewModel {
return contactsBookViewModelFactory.create(initialState)
}
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()
// not exhaustive because it's a sharedAction
else -> {
}
}
}
.disposeOnDestroy()
if (isFirstCreation()) {
addFragment(
R.id.container,
UserListFragment::class.java,
UserListFragmentArgs(
title = "Call transfer",
menuResId = -1
)
)
}
}
private fun openPhoneBook() {
// Check permission first
if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
this,
PERMISSION_REQUEST_CODE_READ_CONTACTS,
0)) {
addFragmentToBackstack(R.id.container, 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.container, 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 {
return Intent(context, CallTransferActivity::class.java).also {
it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId))
}
}
}
}

View file

@ -0,0 +1,50 @@
/*
* 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 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 timber.log.Timber
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState)
: VectorViewModel<CallTransferViewState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: CallTransferViewState): CallTransferViewModel
}
companion object : MvRxViewModelFactory<CallTransferViewModel, CallTransferViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: CallTransferViewState): CallTransferViewModel? {
val activity: CallTransferActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.callTransferViewModelFactory.create(state)
}
}
override fun handle(action: EmptyAction) {}
}

View file

@ -0,0 +1,26 @@
/*
* 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 com.airbnb.mvrx.MvRxState
data class CallTransferViewState(
val callId: String
) : MvRxState {
constructor(args: CallTransferArgs) : this(callId = args.callId)
}

View file

@ -44,9 +44,9 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
class ContactsBookFragment @Inject constructor(
val contactsBookViewModelFactory: ContactsBookViewModel.Factory,
private val contactsBookViewModelFactory: ContactsBookViewModel.Factory,
private val contactsBookController: ContactsBookController
) : VectorBaseFragment<FragmentContactsBookBinding>(), ContactsBookController.Callback {
) : VectorBaseFragment<FragmentContactsBookBinding>(), ContactsBookController.Callback, ContactsBookViewModel.Factory {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentContactsBookBinding {
return FragmentContactsBookBinding.inflate(inflater, container, false)
@ -59,6 +59,10 @@ class ContactsBookFragment @Inject constructor(
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
override fun create(initialState: ContactsBookViewState): ContactsBookViewModel {
return contactsBookViewModelFactory.create(initialState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)

View file

@ -33,6 +33,7 @@ import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.invite.InviteUsersToRoomActivity
import im.vector.app.features.userdirectory.UserListViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
@ -56,17 +57,11 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
companion object : MvRxViewModelFactory<ContactsBookViewModel, ContactsBookViewState> {
override fun create(viewModelContext: ViewModelContext, state: ContactsBookViewState): ContactsBookViewModel? {
return when (viewModelContext) {
is FragmentViewModelContext -> (viewModelContext.fragment() as ContactsBookFragment).contactsBookViewModelFactory.create(state)
is ActivityViewModelContext -> {
when (viewModelContext.activity<FragmentActivity>()) {
is CreateDirectRoomActivity -> viewModelContext.activity<CreateDirectRoomActivity>().contactsBookViewModelFactory.create(state)
is InviteUsersToRoomActivity -> viewModelContext.activity<InviteUsersToRoomActivity>().contactsBookViewModelFactory.create(state)
else -> error("Wrong activity or fragment")
}
}
else -> error("Wrong activity or fragment")
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}

View file

@ -45,6 +45,7 @@ import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedSnackbar
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
@ -57,7 +58,7 @@ import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection
import javax.inject.Inject
class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory, CreateDirectRoomViewModel.Factory, ContactsBookViewModel.Factory {
private val viewModel: CreateDirectRoomViewModel by viewModel()
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
@ -71,9 +72,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
injector.inject(this)
}
override fun create(initialState: UserListViewState): UserListViewModel {
return userListViewModelFactory.create(initialState)
}
override fun create(initialState: UserListViewState) = userListViewModelFactory.create(initialState)
override fun create(initialState: CreateDirectRoomViewState) = createDirectRoomViewModelFactory.create(initialState)
override fun create(initialState: ContactsBookViewState) = contactsBookViewModelFactory.create(initialState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -18,6 +18,7 @@ package im.vector.app.features.createdirect
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
@ -25,6 +26,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.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
@ -50,8 +52,11 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: CreateDirectRoomViewState): CreateDirectRoomViewModel? {
val activity: CreateDirectRoomActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.createDirectRoomViewModelFactory.create(state)
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}

View file

@ -39,6 +39,7 @@ import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.toast
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
@ -53,7 +54,7 @@ import javax.inject.Inject
@Parcelize
data class InviteUsersToRoomArgs(val roomId: String) : Parcelable
class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory, ContactsBookViewModel.Factory, InviteUsersToRoomViewModel.Factory {
private val viewModel: InviteUsersToRoomViewModel by viewModel()
private lateinit var sharedActionViewModel: UserListSharedActionViewModel
@ -67,9 +68,11 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa
injector.inject(this)
}
override fun create(initialState: UserListViewState): UserListViewModel {
return userListViewModelFactory.create(initialState)
}
override fun create(initialState: UserListViewState) = userListViewModelFactory.create(initialState)
override fun create(initialState: ContactsBookViewState) = contactsBookViewModelFactory.create(initialState)
override fun create(initialState: InviteUsersToRoomViewState) = inviteUsersToRoomViewModelFactory.create(initialState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -17,6 +17,7 @@
package im.vector.app.features.invite
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
@ -24,6 +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 io.reactivex.Observable
import org.matrix.android.sdk.api.session.Session
@ -46,8 +48,11 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: InviteUsersToRoomViewState): InviteUsersToRoomViewModel? {
val activity: InviteUsersToRoomActivity = (viewModelContext as ActivityViewModelContext).activity()
return activity.inviteUsersToRoomViewModelFactory.create(state)
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}

View file

@ -34,6 +34,8 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
import im.vector.app.features.call.conference.JitsiCallViewModel
import im.vector.app.features.call.conference.VectorJitsiActivity
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.call.transfer.CallTransferArgs
import im.vector.app.features.createdirect.CreateDirectRoomActivity
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
@ -344,6 +346,11 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent)
}
override fun openCallTransfer(context: Context, callId: String) {
val intent = CallTransferActivity.newIntent(context, callId)
context.startActivity(intent)
}
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)

View file

@ -113,4 +113,6 @@ interface Navigator {
options: ((MutableList<Pair<View, String>>) -> Unit)?)
fun openSearch(context: Context, roomId: String)
fun openCallTransfer(context: Context, callId: String)
}