Merge pull request #3456 from vector-im/feature/fga/dial_pad_tab
Feature/fga/dial pad tab
This commit is contained in:
commit
dfb01a462e
|
@ -51,7 +51,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
|||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||
fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||
return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
|
||||
DaggerSessionComponent
|
||||
.factory()
|
||||
|
|
|
@ -31,9 +31,11 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase
|
|||
*/
|
||||
fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> {
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
// Makes sure we have the latest realm updates, this is important as we sent this information to the server.
|
||||
realm.refresh()
|
||||
RoomSummaryEntity.getDirectRooms(realm)
|
||||
.asSequence()
|
||||
.filter { it.roomId != filterRoomId && it.directUserId != null }
|
||||
.filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() }
|
||||
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix new DMs not always marked as such
|
|
@ -0,0 +1 @@
|
|||
Move the ability to start a call from dialpad directly to a dedicated tab in the home screen.
|
|
@ -116,7 +116,9 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
throwable.localizedMessage
|
||||
}
|
||||
}
|
||||
is DialPadLookup.Failure ->
|
||||
is DialPadLookup.Failure.NumberIsYours ->
|
||||
stringProvider.getString(R.string.cannot_call_yourself)
|
||||
is DialPadLookup.Failure.NoResult ->
|
||||
stringProvider.getString(R.string.call_dial_pad_lookup_error)
|
||||
else -> throwable.localizedMessage
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.app.databinding.BottomSheetCallDialerChoiceBinding
|
||||
|
||||
class DialerChoiceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallDialerChoiceBinding>() {
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetCallDialerChoiceBinding {
|
||||
return BottomSheetCallDialerChoiceBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
var onDialPadClicked: (() -> Unit)? = null
|
||||
var onVoiceCallClicked: (() -> Unit)? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
views.dialerChoiceDialPad.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
onDialPadClicked?.invoke()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
views.dialerChoiceVoiceCall.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
onVoiceCallClicked?.invoke()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,9 +26,9 @@ import androidx.core.widget.ImageViewCompat
|
|||
import androidx.fragment.app.Fragment
|
||||
import com.android.dialer.dialpadview.DialpadView
|
||||
import com.android.dialer.dialpadview.DigitsEditText
|
||||
import com.android.dialer.dialpadview.R
|
||||
import com.google.i18n.phonenumbers.AsYouTypeFormatter
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class DialPadFragment : Fragment() {
|
||||
|
@ -57,7 +57,7 @@ class DialPadFragment : Fragment() {
|
|||
dialpadView.findViewById<View>(R.id.dialpad_key_voicemail).isVisible = false
|
||||
digits = dialpadView.digits as? DigitsEditText
|
||||
digits?.isCursorVisible = cursorVisible
|
||||
digits?.setTextColor(ThemeUtils.getColor(requireContext(), im.vector.app.R.attr.vctr_content_primary))
|
||||
digits?.setTextColor(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_primary))
|
||||
dialpadView.findViewById<View>(R.id.zero).setOnClickListener { append('0') }
|
||||
if (enablePlus) {
|
||||
dialpadView.findViewById<View>(R.id.zero).setOnLongClickListener {
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
package im.vector.app.features.call.dialpad
|
||||
|
||||
import im.vector.app.features.call.lookup.pstnLookup
|
||||
import im.vector.app.features.call.lookup.sipNativeLookup
|
||||
import im.vector.app.features.call.vectorCallService
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import java.lang.IllegalStateException
|
||||
import javax.inject.Inject
|
||||
|
||||
class DialPadLookup @Inject constructor(
|
||||
|
@ -28,13 +29,25 @@ class DialPadLookup @Inject constructor(
|
|||
private val webRtcCallManager: WebRtcCallManager,
|
||||
private val directRoomHelper: DirectRoomHelper
|
||||
) {
|
||||
class Failure : Throwable()
|
||||
sealed class Failure : Throwable() {
|
||||
object NoResult: Failure()
|
||||
object NumberIsYours: Failure()
|
||||
}
|
||||
|
||||
data class Result(val userId: String, val roomId: String)
|
||||
|
||||
suspend fun lookupPhoneNumber(phoneNumber: String): Result {
|
||||
val thirdPartyUser = session.pstnLookup(phoneNumber, webRtcCallManager.supportedPSTNProtocol).firstOrNull() ?: throw IllegalStateException()
|
||||
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
|
||||
return Result(userId = thirdPartyUser.userId, roomId = roomId)
|
||||
session.vectorCallService.protocolChecker.awaitCheckProtocols()
|
||||
val thirdPartyUser = session.pstnLookup(phoneNumber, webRtcCallManager.supportedPSTNProtocol).firstOrNull() ?: throw Failure.NoResult
|
||||
// check to see if this is a virtual user, in which case we should find the native user
|
||||
val nativeUserId = if (webRtcCallManager.supportsVirtualRooms) {
|
||||
val nativeLookupResults = session.sipNativeLookup(thirdPartyUser.userId)
|
||||
nativeLookupResults.firstOrNull()?.userId ?: thirdPartyUser.userId
|
||||
} else {
|
||||
thirdPartyUser.userId
|
||||
}
|
||||
if (nativeUserId == session.myUserId) throw Failure.NumberIsYours
|
||||
val roomId = directRoomHelper.ensureDMExists(nativeUserId)
|
||||
return Result(userId = nativeUserId, roomId = roomId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package im.vector.app.features.call.lookup
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser
|
||||
|
||||
private const val LOOKUP_SUCCESS_FIELD = "lookup_success"
|
||||
|
||||
suspend fun Session.pstnLookup(phoneNumber: String, protocol: String?): List<ThirdPartyUser> {
|
||||
if (protocol == null) return emptyList()
|
||||
return tryOrNull {
|
||||
|
@ -36,7 +39,11 @@ suspend fun Session.sipVirtualLookup(nativeMxid: String): List<ThirdPartyUser> {
|
|||
protocol = PROTOCOL_SIP_VIRTUAL,
|
||||
fields = mapOf("native_mxid" to nativeMxid)
|
||||
)
|
||||
}.orEmpty()
|
||||
}
|
||||
.orEmpty()
|
||||
.filter {
|
||||
(it.fields[LOOKUP_SUCCESS_FIELD] as? Boolean).orFalse()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Session.sipNativeLookup(virtualMxid: String): List<ThirdPartyUser> {
|
||||
|
@ -45,5 +52,9 @@ suspend fun Session.sipNativeLookup(virtualMxid: String): List<ThirdPartyUser> {
|
|||
protocol = PROTOCOL_SIP_NATIVE,
|
||||
fields = mapOf("virtual_mxid" to virtualMxid)
|
||||
)
|
||||
}.orEmpty()
|
||||
}
|
||||
.orEmpty()
|
||||
.filter {
|
||||
(it.fields[LOOKUP_SUCCESS_FIELD] as? Boolean).orFalse()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.home
|
|||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class HomeDetailAction : VectorViewModelAction {
|
||||
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
|
||||
data class SwitchTab(val tab: HomeTab) : HomeDetailAction()
|
||||
object MarkAllRoomsRead : HomeDetailAction()
|
||||
data class StartCallWithPhoneNumber(val phoneNumber: String): HomeDetailAction()
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.iterator
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -41,12 +43,14 @@ import im.vector.app.core.ui.views.KnownCallsViewHolder
|
|||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.room.list.RoomListFragment
|
||||
import im.vector.app.features.home.room.list.RoomListParams
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import im.vector.app.features.popup.VerificationVectorAlert
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
@ -101,6 +105,9 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
withState(viewModel) { state ->
|
||||
menu.iterator().forEach { it.isVisible = state.currentTab is HomeTab.RoomList }
|
||||
}
|
||||
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
@ -123,7 +130,7 @@ class HomeDetailFragment @Inject constructor(
|
|||
|
||||
withState(viewModel) {
|
||||
// Update the navigation view if needed (for when we restore the tabs)
|
||||
views.bottomNavigationView.selectedItemId = it.displayMode.toMenuId()
|
||||
views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId()
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
|
||||
|
@ -137,8 +144,20 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode ->
|
||||
switchDisplayMode(displayMode)
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::currentTab) { currentTab ->
|
||||
updateUIForTab(currentTab)
|
||||
}
|
||||
|
||||
viewModel.selectSubscribe(this, HomeDetailViewState::showDialPadTab) { showDialPadTab ->
|
||||
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab)
|
||||
}
|
||||
|
||||
viewModel.observeViewEvents { viewEvent ->
|
||||
when (viewEvent) {
|
||||
HomeDetailViewEvents.CallStarted -> dismissLoadingDialog()
|
||||
is HomeDetailViewEvents.FailToCall -> showFailure(viewEvent.failure)
|
||||
HomeDetailViewEvents.Loading -> showLoadingDialog()
|
||||
}
|
||||
}
|
||||
|
||||
unknownDeviceDetectorSharedViewModel.subscribe { state ->
|
||||
|
@ -179,20 +198,8 @@ class HomeDetailFragment @Inject constructor(
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
// update notification tab if needed
|
||||
checkNotificationTabStatus()
|
||||
}
|
||||
|
||||
private fun checkNotificationTabStatus() {
|
||||
val wasVisible = views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible
|
||||
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
|
||||
if (wasVisible && !vectorPreferences.labAddNotificationTab()) {
|
||||
// As we hide it check if it's not the current item!
|
||||
withState(viewModel) {
|
||||
if (it.displayMode.toMenuId() == R.id.bottom_action_notification) {
|
||||
viewModel.handle(HomeDetailAction.SwitchDisplayMode(RoomListDisplayMode.PEOPLE))
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
|
||||
callManager.checkForProtocolsSupportIfNeeded()
|
||||
}
|
||||
|
||||
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {
|
||||
|
@ -321,12 +328,13 @@ class HomeDetailFragment @Inject constructor(
|
|||
private fun setupBottomNavigationView() {
|
||||
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
|
||||
views.bottomNavigationView.setOnNavigationItemSelectedListener {
|
||||
val displayMode = when (it.itemId) {
|
||||
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
|
||||
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
|
||||
else -> RoomListDisplayMode.NOTIFICATIONS
|
||||
val tab = when (it.itemId) {
|
||||
R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE)
|
||||
R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS)
|
||||
R.id.bottom_action_notification -> HomeTab.RoomList(RoomListDisplayMode.NOTIFICATIONS)
|
||||
else -> HomeTab.DialPad
|
||||
}
|
||||
viewModel.handle(HomeDetailAction.SwitchDisplayMode(displayMode))
|
||||
viewModel.handle(HomeDetailAction.SwitchTab(tab))
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -342,13 +350,14 @@ class HomeDetailFragment @Inject constructor(
|
|||
// }
|
||||
}
|
||||
|
||||
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
|
||||
views.groupToolbarTitleView.setText(displayMode.titleRes)
|
||||
updateSelectedFragment(displayMode)
|
||||
private fun updateUIForTab(tab: HomeTab) {
|
||||
views.groupToolbarTitleView.setText(tab.titleRes)
|
||||
updateSelectedFragment(tab)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
|
||||
val fragmentTag = "FRAGMENT_TAG_${displayMode.name}"
|
||||
private fun updateSelectedFragment(tab: HomeTab) {
|
||||
val fragmentTag = "FRAGMENT_TAG_$tab"
|
||||
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
|
||||
childFragmentManager.commitTransaction {
|
||||
childFragmentManager.fragments
|
||||
|
@ -357,14 +366,49 @@ class HomeDetailFragment @Inject constructor(
|
|||
detach(it)
|
||||
}
|
||||
if (fragmentToShow == null) {
|
||||
val params = RoomListParams(displayMode)
|
||||
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
|
||||
when (tab) {
|
||||
is HomeTab.RoomList -> {
|
||||
val params = RoomListParams(tab.displayMode)
|
||||
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
|
||||
}
|
||||
is HomeTab.DialPad -> {
|
||||
add(R.id.roomListContainer, createDialPadFragment())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tab is HomeTab.DialPad) {
|
||||
(fragmentToShow as? DialPadFragment)?.applyCallback()
|
||||
}
|
||||
attach(fragmentToShow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDialPadFragment(): Fragment {
|
||||
val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name)
|
||||
return (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()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTabVisibilitySafely(tabId: Int, isVisible: Boolean) {
|
||||
val wasVisible = views.bottomNavigationView.menu.findItem(tabId).isVisible
|
||||
views.bottomNavigationView.menu.findItem(tabId).isVisible = isVisible
|
||||
if (wasVisible && !isVisible) {
|
||||
// As we hide it check if it's not the current item!
|
||||
withState(viewModel) {
|
||||
if (it.currentTab.toMenuId() == tabId) {
|
||||
viewModel.handle(HomeDetailAction.SwitchTab(HomeTab.RoomList(RoomListDisplayMode.PEOPLE)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* KeysBackupBanner Listener
|
||||
* ========================================================================================== */
|
||||
|
@ -399,10 +443,13 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun RoomListDisplayMode.toMenuId() = when (this) {
|
||||
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
||||
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
||||
else -> R.id.bottom_action_notification
|
||||
private fun HomeTab.toMenuId() = when (this) {
|
||||
is HomeTab.DialPad -> R.id.bottom_action_dial_pad
|
||||
is HomeTab.RoomList -> when (displayMode) {
|
||||
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
||||
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
||||
else -> R.id.bottom_action_notification
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTapToReturnToCall() {
|
||||
|
@ -421,6 +468,16 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun DialPadFragment.applyCallback(): DialPadFragment {
|
||||
callback = object : DialPadFragment.Callback {
|
||||
override fun onOkClicked(formatted: String?, raw: String?) {
|
||||
if (raw.isNullOrEmpty()) return
|
||||
viewModel.handle(HomeDetailAction.StartCallWithPhoneNumber(raw))
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel {
|
||||
return serverBackupStatusViewModelFactory.create(initialState)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.home
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class HomeDetailViewEvents : VectorViewEvents {
|
||||
object Loading : HomeDetailViewEvents()
|
||||
object CallStarted : HomeDetailViewEvents()
|
||||
data class FailToCall(val failure: Throwable) : HomeDetailViewEvents()
|
||||
}
|
|
@ -26,8 +26,11 @@ import dagger.assisted.AssistedInject
|
|||
import im.vector.app.AppStateHandler
|
||||
import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.di.HasScreenInjector
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -51,8 +54,11 @@ import java.util.concurrent.TimeUnit
|
|||
class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
|
||||
private val session: Session,
|
||||
private val uiStateRepository: UiStateRepository,
|
||||
private val callManager: WebRtcCallManager,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val appStateHandler: AppStateHandler)
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
||||
CallProtocolsChecker.Listener {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
@ -64,7 +70,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
override fun initialState(viewModelContext: ViewModelContext): HomeDetailViewState? {
|
||||
val uiStateRepository = (viewModelContext.activity as HasScreenInjector).injector().uiStateRepository()
|
||||
return HomeDetailViewState(
|
||||
displayMode = uiStateRepository.getDisplayMode()
|
||||
currentTab = HomeTab.RoomList(uiStateRepository.getDisplayMode())
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -79,7 +85,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
observeSyncState()
|
||||
observeRoomGroupingMethod()
|
||||
observeRoomSummaries()
|
||||
|
||||
updateShowDialPadTab()
|
||||
callManager.addProtocolsCheckerListener(this)
|
||||
session.rx().liveUser(session.myUserId).execute {
|
||||
copy(
|
||||
myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem()
|
||||
|
@ -89,18 +96,48 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
|
||||
override fun handle(action: HomeDetailAction) {
|
||||
when (action) {
|
||||
is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action)
|
||||
HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||
is HomeDetailAction.SwitchTab -> handleSwitchTab(action)
|
||||
HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||
is HomeDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSwitchDisplayMode(action: HomeDetailAction.SwitchDisplayMode) = withState { state ->
|
||||
if (state.displayMode != action.displayMode) {
|
||||
setState {
|
||||
copy(displayMode = action.displayMode)
|
||||
private fun handleStartCallWithPhoneNumber(action: HomeDetailAction.StartCallWithPhoneNumber) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_viewEvents.post(HomeDetailViewEvents.Loading)
|
||||
val result = DialPadLookup(session, callManager, directRoomHelper).lookupPhoneNumber(action.phoneNumber)
|
||||
callManager.startOutgoingCall(result.roomId, result.userId, isVideoCall = false)
|
||||
_viewEvents.post(HomeDetailViewEvents.CallStarted)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(HomeDetailViewEvents.FailToCall(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiStateRepository.storeDisplayMode(action.displayMode)
|
||||
private fun handleSwitchTab(action: HomeDetailAction.SwitchTab) = withState { state ->
|
||||
if (state.currentTab != action.tab) {
|
||||
setState {
|
||||
copy(currentTab = action.tab)
|
||||
}
|
||||
if (action.tab is HomeTab.RoomList) {
|
||||
uiStateRepository.storeDisplayMode(action.tab.displayMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
callManager.removeProtocolsCheckerListener(this)
|
||||
}
|
||||
|
||||
override fun onPSTNSupportUpdated() {
|
||||
updateShowDialPadTab()
|
||||
}
|
||||
|
||||
private fun updateShowDialPadTab() {
|
||||
setState {
|
||||
copy(showDialPadTab = callManager.supportsPSTNProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,11 +175,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
private fun observeRoomGroupingMethod() {
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.subscribe {
|
||||
setState {
|
||||
copy(
|
||||
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
@ -165,7 +202,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
// TODO!!
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId
|
||||
val dmInvites = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
|
||||
package im.vector.app.features.home
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.RoomGroupingMethod
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
|
@ -28,7 +30,7 @@ data class HomeDetailViewState(
|
|||
val roomGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
|
||||
val myMatrixItem: MatrixItem? = null,
|
||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,
|
||||
val currentTab: HomeTab = HomeTab.RoomList(RoomListDisplayMode.PEOPLE),
|
||||
val notificationCountCatchup: Int = 0,
|
||||
val notificationHighlightCatchup: Boolean = false,
|
||||
val notificationCountPeople: Int = 0,
|
||||
|
@ -36,5 +38,11 @@ data class HomeDetailViewState(
|
|||
val notificationCountRooms: Int = 0,
|
||||
val notificationHighlightRooms: Boolean = false,
|
||||
val hasUnreadMessages: Boolean = false,
|
||||
val syncState: SyncState = SyncState.Idle
|
||||
val syncState: SyncState = SyncState.Idle,
|
||||
val showDialPadTab: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
sealed class HomeTab(@StringRes val titleRes: Int) {
|
||||
data class RoomList(val displayMode: RoomListDisplayMode) : HomeTab(displayMode.titleRes)
|
||||
object DialPad : HomeTab(R.string.call_dial_pad_title)
|
||||
}
|
||||
|
|
|
@ -73,7 +73,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||
|
||||
object ResendAll : RoomDetailAction()
|
||||
|
||||
data class StartCallWithPhoneNumber(val phoneNumber: String, val videoCall: Boolean): RoomDetailAction()
|
||||
data class StartCall(val isVideo: Boolean) : RoomDetailAction()
|
||||
data class AcceptCall(val callId: String): RoomDetailAction()
|
||||
object EndCall : RoomDetailAction()
|
||||
|
|
|
@ -320,7 +320,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
startCallActivityResultLauncher = startCallActivityResultLauncher,
|
||||
showDialogWithMessage = ::showDialogWithMessage,
|
||||
onTapToReturnToCall = ::onTapToReturnToCall
|
||||
).register()
|
||||
)
|
||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||
setupToolbar(views.roomToolbar)
|
||||
setupRecyclerView()
|
||||
|
|
|
@ -39,7 +39,6 @@ import im.vector.app.core.mvrx.runCatchingToAsync
|
|||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.call.conference.JitsiService
|
||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.command.CommandParser
|
||||
|
@ -176,7 +175,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
observeMyRoomMember()
|
||||
observeActiveRoomWidgets()
|
||||
observePowerLevel()
|
||||
updateShowDialerOptionState()
|
||||
room.getRoomSummaryLive()
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
|
||||
|
@ -301,7 +299,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
|
@ -327,17 +324,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleStartCallWithPhoneNumber(action: RoomDetailAction.StartCallWithPhoneNumber) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = DialPadLookup(session, callManager, directRoomHelper).lookupPhoneNumber(action.phoneNumber)
|
||||
callManager.startOutgoingCall(result.roomId, result.userId, action.videoCall)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptCall(action: RoomDetailAction.AcceptCall) {
|
||||
callManager.getCallById(action.callId)?.also {
|
||||
_viewEvents.post(RoomDetailViewEvents.DisplayAndAcceptCall(it))
|
||||
|
@ -1491,16 +1477,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(RoomDetailViewEvents.OnNewTimelineEvents(eventIds))
|
||||
}
|
||||
|
||||
override fun onPSTNSupportUpdated() {
|
||||
updateShowDialerOptionState()
|
||||
}
|
||||
|
||||
private fun updateShowDialerOptionState() {
|
||||
setState {
|
||||
copy(showDialerOption = callManager.supportsPSTNProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
roomSummariesHolder.remove(room.roomId)
|
||||
timeline.dispose()
|
||||
|
|
|
@ -75,7 +75,6 @@ data class RoomDetailViewState(
|
|||
val canInvite: Boolean = true,
|
||||
val isAllowedToManageWidgets: Boolean = false,
|
||||
val isAllowedToStartWebRTCCall: Boolean = true,
|
||||
val showDialerOption: Boolean = false,
|
||||
val hasFailedSending: Boolean = false
|
||||
) : MvRxState {
|
||||
|
||||
|
|
|
@ -16,26 +16,18 @@
|
|||
|
||||
package im.vector.app.features.home.room.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.Restorable
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.features.call.DialerChoiceBottomSheet
|
||||
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
|
||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
|
||||
private const val DIALER_OPTION_TAG = "DIALER_OPTION_TAG"
|
||||
private const val DIAL_PAD_TAG = "DIAL_PAD_TAG"
|
||||
|
||||
class StartCallActionsHandler(
|
||||
private val roomId: String,
|
||||
private val fragment: Fragment,
|
||||
|
@ -44,52 +36,20 @@ class StartCallActionsHandler(
|
|||
private val roomDetailViewModel: RoomDetailViewModel,
|
||||
private val startCallActivityResultLauncher: ActivityResultLauncher<Array<String>>,
|
||||
private val showDialogWithMessage: (String) -> Unit,
|
||||
private val onTapToReturnToCall: () -> Unit): Restorable {
|
||||
private val onTapToReturnToCall: () -> Unit) {
|
||||
|
||||
fun onVideoCallClicked() {
|
||||
handleCallRequest(true)
|
||||
}
|
||||
|
||||
fun onVoiceCallClicked() = withState(roomDetailViewModel) {
|
||||
if (it.showDialerOption) {
|
||||
displayDialerChoiceBottomSheet()
|
||||
} else {
|
||||
handleCallRequest(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DialerChoiceBottomSheet.applyListeners(): DialerChoiceBottomSheet {
|
||||
onDialPadClicked = ::displayDialPadBottomSheet
|
||||
onVoiceCallClicked = { handleCallRequest(false) }
|
||||
return this
|
||||
}
|
||||
|
||||
private fun CallDialPadBottomSheet.applyCallback(): CallDialPadBottomSheet {
|
||||
callback = object : DialPadFragment.Callback {
|
||||
override fun onOkClicked(formatted: String?, raw: String?) {
|
||||
if (raw.isNullOrEmpty()) return
|
||||
roomDetailViewModel.handle(RoomDetailAction.StartCallWithPhoneNumber(raw, false))
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun displayDialerChoiceBottomSheet() {
|
||||
DialerChoiceBottomSheet()
|
||||
.applyListeners()
|
||||
.show(fragment.parentFragmentManager, DIALER_OPTION_TAG)
|
||||
}
|
||||
|
||||
private fun displayDialPadBottomSheet() {
|
||||
CallDialPadBottomSheet.newInstance(true)
|
||||
.applyCallback()
|
||||
.show(fragment.parentFragmentManager, DIAL_PAD_TAG)
|
||||
fun onVoiceCallClicked() {
|
||||
handleCallRequest(false)
|
||||
}
|
||||
|
||||
private fun handleCallRequest(isVideoCall: Boolean) = withState(roomDetailViewModel) { state ->
|
||||
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
|
||||
when (roomSummary.joinedMembersCount) {
|
||||
1 -> {
|
||||
1 -> {
|
||||
val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0
|
||||
if (pendingInvite) {
|
||||
// wait for other to join
|
||||
|
@ -99,7 +59,7 @@ class StartCallActionsHandler(
|
|||
showDialogWithMessage(fragment.getString(R.string.cannot_call_yourself))
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
2 -> {
|
||||
val currentCall = callManager.getCurrentCall()
|
||||
if (currentCall != null) {
|
||||
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||
|
@ -190,13 +150,4 @@ class StartCallActionsHandler(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) = Unit
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
|
||||
if (savedInstanceState != null) {
|
||||
(fragment.parentFragmentManager.findFragmentByTag(DIALER_OPTION_TAG) as? DialerChoiceBottomSheet)?.applyListeners()
|
||||
(fragment.parentFragmentManager.findFragmentByTag(DIAL_PAD_TAG) as? CallDialPadBottomSheet)?.applyCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,11 @@
|
|||
android:title="@string/bottom_action_notification"
|
||||
android:visible="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/bottom_action_dial_pad"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_call_dial_pad"
|
||||
android:title="@string/call_dial_pad_title"
|
||||
android:visible="false" />
|
||||
|
||||
</menu>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="DialpadKeyNumberStyle">
|
||||
<item name="android:textColor">?attr/vctr_content_primary</item>
|
||||
<item name="android:textSize">@dimen/dialpad_key_numbers_default_size</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_key_number_default_margin_bottom</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyLettersStyle">
|
||||
<item name="android:textColor">?attr/vctr_content_secondary</item>
|
||||
<item name="android:textSize">@dimen/dialpad_key_letters_size</item>
|
||||
<item name="android:fontFamily">sans-serif-regular</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:gravity">center_horizontal</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyPoundStyle" parent="DialpadKeyNumberStyle">
|
||||
<item name="android:textSize">@dimen/dialpad_key_pound_size</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyStarStyle" parent="DialpadKeyNumberStyle">
|
||||
<item name="android:textSize">@dimen/dialpad_key_star_size</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -1,12 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="DialpadKeyNumberStyle">
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
<item name="android:textSize">@dimen/dialpad_key_numbers_default_size</item>
|
||||
<item name="android:fontFamily">sans-serif-light</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_key_number_default_margin_bottom</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyLettersStyle">
|
||||
<item name="android:textColor">?attr/vctr_content_secondary</item>
|
||||
<item name="android:textSize">@dimen/dialpad_key_letters_size</item>
|
||||
<item name="android:fontFamily">sans-serif-regular</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:gravity">center_horizontal</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyPoundStyle" parent="DialpadKeyNumberStyle">
|
||||
<item name="android:textSize">@dimen/dialpad_key_pound_size</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
|
||||
</style>
|
||||
|
||||
<style name="DialpadKeyStarStyle" parent="DialpadKeyNumberStyle">
|
||||
<item name="android:textSize">@dimen/dialpad_key_star_size</item>
|
||||
<item name="android:layout_marginBottom">@dimen/dialpad_symbol_margin_bottom</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue