From 804afc9a1d59a4dbf76a3fa03f52f303484ecad2 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 15:19:49 +0100 Subject: [PATCH 1/4] Fix issues with matrix.to deep linking --- vector/src/main/AndroidManifest.xml | 6 +- .../vector/app/features/home/HomeActivity.kt | 61 +++++- .../app/features/matrixto/MatrixToAction.kt | 24 +++ .../features/matrixto/MatrixToBottomSheet.kt | 118 +++++++++-- .../matrixto/MatrixToBottomSheetState.kt | 27 +++ .../matrixto/MatrixToBottomSheetViewModel.kt | 192 ++++++++++++++++++ .../features/matrixto/MatrixToViewEvents.kt | 24 +++ .../permalink/PermalinkHandlerActivity.kt | 31 +-- .../app/features/usercode/UserCodeActivity.kt | 7 +- .../usercode/UserCodeSharedViewModel.kt | 45 ++-- .../layout/bottom_sheet_matrix_to_card.xml | 34 +++- 11 files changed, 511 insertions(+), 58 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 7d2ca11813..e9bd03cb4b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -81,7 +81,8 @@ android:resource="@xml/shortcuts" /> - + - + - diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 32a4af1b1b..e3e942fce9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -38,8 +38,12 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.utils.toast import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.notifications.NotificationDrawerManager +import im.vector.app.features.permalink.NavigationInterceptor +import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.popup.DefaultVectorAlert import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert @@ -50,10 +54,12 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import im.vector.app.push.fcm.FcmHelper +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.session.InitialSyncProgressService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.util.MatrixItem import timber.log.Timber import javax.inject.Inject @@ -64,7 +70,8 @@ data class HomeActivityArgs( val accountCreation: Boolean ) : Parcelable -class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory { +class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory, + NavigationInterceptor { private lateinit var sharedActionViewModel: HomeSharedActionViewModel @@ -82,6 +89,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var unknownDeviceViewModelFactory: UnknownDeviceDetectorSharedViewModel.Factory + @Inject lateinit var permalinkHandler: PermalinkHandler private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { @@ -117,9 +125,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } @@ -136,20 +144,42 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) - HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() - is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() + + if (isFirstCreation()) { + handleIntent(intent) + } + } + + private fun handleIntent(intent: Intent?) { + intent?.dataString?.let { deepLink -> + if (!deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) return@let + + permalinkHandler.launch(this, deepLink, + navigationInterceptor = this, + buildTask = true) + // .delay(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { isHandled -> + if (!isHandled) { + toast(R.string.permalink_malformed) + } + } + .disposeOnDestroy() + } } private fun renderState(state: HomeActivityViewState) { when (val status = state.initialSyncProgressServiceStatus) { - is InitialSyncProgressService.Status.Idle -> { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { @@ -270,6 +300,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet if (intent?.getParcelableExtra(MvRx.KEY_ARG)?.clearNotification == true) { notificationDrawerManager.clearAllEvents() } + handleIntent(intent) } override fun onDestroy() { @@ -313,11 +344,11 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet bugReporter.openBugReportScreen(this, false) return true } - R.id.menu_home_filter -> { + R.id.menu_home_filter -> { navigator.openRoomsFiltering(this) return true } - R.id.menu_home_setting -> { + R.id.menu_home_setting -> { navigator.openSettings(this) return true } @@ -334,6 +365,18 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } + override fun navToMemberProfile(userId: String): Boolean { + val listener = object : MatrixToBottomSheet.InteractionListener { + override fun navigateToRoom(roomId: String) { + navigator.openRoom(this@HomeActivity, roomId) + } + } + // TODO check if there is already one?? + MatrixToBottomSheet.withUserId(userId, listener) + .show(supportFragmentManager, "HA#MatrixToBottomSheet") + return true + } + companion object { fun newIntent(context: Context, clearNotification: Boolean = false, accountCreation: Boolean = false): Intent { val args = HomeActivityArgs( diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt new file mode 100644 index 0000000000..e1c6800494 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt @@ -0,0 +1,24 @@ +/* + * 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.matrixto + +import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.util.MatrixItem + +sealed class MatrixToAction : VectorViewModelAction { + data class StartChattingWithUser(val matrixItem: MatrixItem) : MatrixToAction() +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 91c09ef21a..41020ea404 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -17,23 +17,38 @@ package im.vector.app.features.matrixto import android.os.Bundle +import android.os.Parcelable import android.view.View +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.home.AvatarRenderer +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_matrix_to_card.* -import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject -class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottomSheetDialogFragment() { +class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { + + @Parcelize + data class MatrixToArgs( + val matrixToLink: String?, + val userId: String? + ) : Parcelable @Inject lateinit var avatarRenderer: AvatarRenderer - interface InteractionListener { - fun didTapStartMessage(matrixItem: MatrixItem) - } + @Inject + lateinit var matrixToBottomSheetViewModelFactory: MatrixToBottomSheetViewModel.Factory override fun injectWith(injector: ScreenComponent) { injector.inject(this) @@ -43,21 +58,100 @@ class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottom override fun getLayoutResId() = R.layout.bottom_sheet_matrix_to_card + private val viewModel by fragmentViewModel(MatrixToBottomSheetViewModel::class) + + interface InteractionListener { + fun navigateToRoom(roomId: String) + } + + override fun invalidate() = withState(viewModel) { state -> + super.invalidate() + when (val item = state.matrixItem) { + Uninitialized -> { + matrixToCardContentLoading.isVisible = false + matrixToCardUserContentVisibility.isVisible = false + } + is Loading -> { + matrixToCardContentLoading.isVisible = true + matrixToCardUserContentVisibility.isVisible = false + } + is Success -> { + matrixToCardContentLoading.isVisible = false + matrixToCardUserContentVisibility.isVisible = true + matrixToCardNameText.setTextOrHide(item.invoke().displayName) + matrixToCardUserIdText.setTextOrHide(item.invoke().id) + avatarRenderer.render(item.invoke(), matrixToCardAvatar) + } + is Fail -> { + // TODO display some error copy? + dismiss() + } + } + + when (state.startChattingState) { + Uninitialized -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = false + } + is Success -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = true + } + is Fail -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = true + // TODO display some error copy? + dismiss() + } + is Loading -> { + matrixToCardButtonLoading.isVisible = true + matrixToCardSendMessageButton.isInvisible = true + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) matrixToCardSendMessageButton.debouncedClicks { - interactionListener?.didTapStartMessage(matrixItem) - dismiss() + withState(viewModel) { + it.matrixItem.invoke()?.let { item -> + viewModel.handle(MatrixToAction.StartChattingWithUser(item)) + } + } } - matrixToCardNameText.setTextOrHide(matrixItem.displayName) - matrixToCardUserIdText.setTextOrHide(matrixItem.id) - avatarRenderer.render(matrixItem, matrixToCardAvatar) + viewModel.observeViewEvents { + when (it) { + is MatrixToViewEvents.NavigateToRoom -> { + interactionListener?.navigateToRoom(it.roomId) + dismiss() + } + MatrixToViewEvents.Dismiss -> dismiss() + } + } } companion object { - fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet { - return MatrixToBottomSheet(matrixItem).apply { + fun withLink(matrixToLink: String, listener: InteractionListener?): MatrixToBottomSheet { + return MatrixToBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( + matrixToLink = matrixToLink, + userId = null + )) + } + interactionListener = listener + } + } + + fun withUserId(userId: String, listener: InteractionListener?): MatrixToBottomSheet { + return MatrixToBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( + matrixToLink = null, + userId = userId + )) + } interactionListener = listener } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt new file mode 100644 index 0000000000..0080b28c66 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -0,0 +1,27 @@ +/* + * 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.matrixto + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.util.MatrixItem + +data class MatrixToBottomSheetState( + val matrixItem: Async = Uninitialized, + val startChattingState: Async = Uninitialized +) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt new file mode 100644 index 0000000000..06cae3218b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -0,0 +1,192 @@ +/* + * 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.matrixto + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.R +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.raw.wellknown.getElementWellknown +import im.vector.app.features.raw.wellknown.isE2EByDefault +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.profile.ProfileService +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.internal.util.awaitCallback + +class MatrixToBottomSheetViewModel @AssistedInject constructor( + @Assisted initialState: MatrixToBottomSheetState, + @Assisted val args: MatrixToBottomSheet.MatrixToArgs, + private val session: Session, + private val stringProvider: StringProvider, + private val rawService: RawService) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: MatrixToBottomSheetState, + args: MatrixToBottomSheet.MatrixToArgs): MatrixToBottomSheetViewModel + } + + init { + setState { + copy(matrixItem = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + resolveLink() + } + } + + private suspend fun resolveLink() { + when { + args.matrixToLink != null -> { + val linkedId = PermalinkParser.parse(args.matrixToLink) + if (linkedId is PermalinkData.FallbackLink) { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), + startChattingState = Uninitialized + ) + } + return + } + + when (linkedId) { + is PermalinkData.UserLink -> { + val user = resolveUser(linkedId.userId) + setState { + copy( + matrixItem = Success(user.toMatrixItem()), + startChattingState = Success(Unit) + ) + } + } + is PermalinkData.RoomLink -> TODO() + is PermalinkData.GroupLink -> { + // not yet supported + } + is PermalinkData.FallbackLink -> { + } + } + } + args.userId != null -> { + val user = resolveUser(args.userId) + + setState { + copy( + matrixItem = Success(user.toMatrixItem()), + startChattingState = Success(Unit) + ) + } + } + else -> { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), + startChattingState = Uninitialized + ) + } + } + } + } + + private suspend fun resolveUser(userId: String): User { + return (session.getUser(userId) + ?: tryOrNull { + awaitCallback { + session.getProfile(userId, it) + } + }?.let { + User(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) + } + // Create raw Uxid in case the user is not searchable + ?: User(userId, null, null)) + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { + val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + val args: MatrixToBottomSheet.MatrixToArgs = viewModelContext.args() + + return fragment.matrixToBottomSheetViewModelFactory.create(state, args) + } + } + + override fun handle(action: MatrixToAction) { + when (action) { + is MatrixToAction.StartChattingWithUser -> handleStartChatting(action) + }.exhaustive + } + + private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { + val mxId = action.matrixItem.id + val existing = session.getExistingDirectRoomWithUser(mxId) + if (existing != null) { + // navigate to this room + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(existing)) + } else { + setState { + copy(startChattingState = Loading()) + } + // we should create the room then navigate + viewModelScope.launch(Dispatchers.IO) { + val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) + ?.isE2EByDefault() + ?: true + + val roomParams = CreateRoomParams() + .apply { + invitedUserIds.add(mxId) + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault + } + + val roomId = + try { + awaitCallback { session.createRoom(roomParams, it) }.also { + setState { + copy(startChattingState = Success(Unit)) + } + } + } catch (failure: Throwable) { + setState { + copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) + } + return@launch + } + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(roomId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt new file mode 100644 index 0000000000..f9491fd361 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt @@ -0,0 +1,24 @@ +/* + * 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.matrixto + +import im.vector.app.core.platform.VectorViewEvents + +sealed class MatrixToViewEvents : VectorViewEvents { + data class NavigateToRoom(val roomId: String) : MatrixToViewEvents() + object Dismiss : MatrixToViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt index e005dd06c5..e8064aaec5 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt @@ -23,11 +23,9 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.utils.toast +import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.LoadingFragment import im.vector.app.features.login.LoginActivity -import io.reactivex.android.schedulers.AndroidSchedulers -import java.util.concurrent.TimeUnit import javax.inject.Inject class PermalinkHandlerActivity : VectorBaseActivity() { @@ -45,23 +43,28 @@ class PermalinkHandlerActivity : VectorBaseActivity() { if (isFirstCreation()) { replaceFragment(R.id.simpleFragmentContainer, LoadingFragment::class.java) } + handleIntent() + } + + private fun handleIntent() { // If we are not logged in, open login screen. // In the future, we might want to relaunch the process after login. if (!sessionHolder.hasActiveSession()) { startLoginActivity() return } - val uri = intent.dataString - permalinkHandler.launch(this, uri, buildTask = true) - .delay(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { isHandled -> - if (!isHandled) { - toast(R.string.permalink_malformed) - } - finish() - } - .disposeOnDestroy() + // We forward intent to HomeActivity (singleTask) to avoid the dueling app problem + // https://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances + intent.setClass(this, HomeActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + startActivity(intent) + + finish() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + handleIntent() } private fun startLoginActivity() { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index ffef98d544..d6279470ae 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -36,7 +36,6 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.matrixto.MatrixToBottomSheet import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_simple.* -import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject import kotlin.reflect.KClass @@ -72,7 +71,7 @@ class UserCodeActivity UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) - MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet") + MatrixToBottomSheet.withUserId(mode.matrixItem.id, this).show(supportFragmentManager, "MatrixToBottomSheet") } } } @@ -104,8 +103,8 @@ class UserCodeActivity } } - override fun didTapStartMessage(matrixItem: MatrixItem) { - sharedViewModel.handle(UserCodeActions.StartChattingWithUser(matrixItem)) + override fun navigateToRoom(roomId: String) { + navigator.openRoom(this, roomId) } override fun onBackPressed() = withState(sharedViewModel) { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index abef15a1ba..93b198f9d7 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -30,12 +30,15 @@ import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback @@ -72,12 +75,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) - is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted) - UserCodeActions.ShareByText -> handleShareByText() + UserCodeActions.ShareByText -> handleShareByText() } } @@ -139,13 +142,21 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) viewModelScope.launch(Dispatchers.IO) { when (linkedId) { - is PermalinkData.RoomLink -> TODO() - is PermalinkData.UserLink -> { - val user = session.getUser(linkedId.userId) ?: awaitCallback> { - session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it) - }.firstOrNull { it.userId == linkedId.userId } - // Create raw Uxid in case the user is not searchable - ?: User(linkedId.userId, null, null) + is PermalinkData.RoomLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } + is PermalinkData.UserLink -> { + val user = session.getUser(linkedId.userId) + ?: tryOrNull { + awaitCallback { + session.getProfile(linkedId.userId, it) + } + }?.let { + User(linkedId.userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) + } + // Create raw Uxid in case the user is not searchable + ?: User(linkedId.userId, null, null) setState { copy( @@ -153,8 +164,14 @@ class UserCodeSharedViewModel @AssistedInject constructor( ) } } - is PermalinkData.GroupLink -> TODO() - is PermalinkData.FallbackLink -> TODO() + is PermalinkData.GroupLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } + is PermalinkData.FallbackLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } } _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) } diff --git a/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml index b8c81ded3a..d051bd7c98 100644 --- a/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml +++ b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml @@ -3,13 +3,24 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:minHeight="200dp"> + + + + + + From 8e6e6736a37f3ec8d180b111493d40129ec3ffc5 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 17:57:18 +0100 Subject: [PATCH 2/4] Code review --- .../sdk/api/session/user/UserService.kt | 5 +++ .../session/user/DefaultUserService.kt | 31 ++++++++++++++ .../matrixto/MatrixToBottomSheetState.kt | 10 ++++- .../matrixto/MatrixToBottomSheetViewModel.kt | 40 ++++++++----------- .../usercode/UserCodeSharedViewModel.kt | 17 +++----- 5 files changed, 68 insertions(+), 35 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index 2cfc4b731f..ab85f979bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -35,6 +35,11 @@ interface UserService { */ fun getUser(userId: String): User? + /** + * Try to resolve user from known users, or using profile api + */ + fun resolveUser(userId: String, callback: MatrixCallback) + /** * Search list of users on server directory. * @param search the searched term diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt index d2eb7a14ef..1740956915 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt @@ -19,10 +19,13 @@ package org.matrix.android.sdk.internal.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import org.matrix.android.sdk.internal.session.user.model.SearchUserTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -32,12 +35,40 @@ import javax.inject.Inject internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource, private val searchUserTask: SearchUserTask, private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, + private val getProfileInfoTask: GetProfileInfoTask, private val taskExecutor: TaskExecutor) : UserService { override fun getUser(userId: String): User? { return userDataSource.getUser(userId) } + override fun resolveUser(userId: String, callback: MatrixCallback) { + val known = getUser(userId) + if (known != null) { + callback.onSuccess(known) + } else { + val params = GetProfileInfoTask.Params(userId) + getProfileInfoTask + .configureWith(params) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: JsonDict) { + callback.onSuccess( + User( + userId, + data[ProfileService.DISPLAY_NAME_KEY] as? String, + data[ProfileService.AVATAR_URL_KEY] as? String) + ) + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + } + } + .executeBy(taskExecutor) + } + } + override fun getUserLive(userId: String): LiveData> { return userDataSource.getUserLive(userId) } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 0080b28c66..9ec2071a94 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -22,6 +22,14 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.util.MatrixItem data class MatrixToBottomSheetState( + val userId: String? = null, + val deepLink: String? = null, val matrixItem: Async = Uninitialized, val startChattingState: Async = Uninitialized -) : MvRxState +) : MvRxState { + + constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( + userId = args.userId, + deepLink = args.matrixToLink + ) +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 06cae3218b..5f06bc6edd 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -39,24 +39,20 @@ import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser -import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback class MatrixToBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: MatrixToBottomSheetState, - @Assisted val args: MatrixToBottomSheet.MatrixToArgs, private val session: Session, private val stringProvider: StringProvider, private val rawService: RawService) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: MatrixToBottomSheetState, - args: MatrixToBottomSheet.MatrixToArgs): MatrixToBottomSheetViewModel + fun create(initialState: MatrixToBottomSheetState): MatrixToBottomSheetViewModel } init { @@ -64,14 +60,14 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( copy(matrixItem = Loading()) } viewModelScope.launch(Dispatchers.IO) { - resolveLink() + resolveLink(initialState) } } - private suspend fun resolveLink() { + private suspend fun resolveLink(initialState: MatrixToBottomSheetState) { when { - args.matrixToLink != null -> { - val linkedId = PermalinkParser.parse(args.matrixToLink) + initialState.deepLink != null -> { + val linkedId = PermalinkParser.parse(initialState.deepLink) if (linkedId is PermalinkData.FallbackLink) { setState { copy( @@ -92,7 +88,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - is PermalinkData.RoomLink -> TODO() + is PermalinkData.RoomLink -> { + // not yet supported + } is PermalinkData.GroupLink -> { // not yet supported } @@ -100,8 +98,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } } } - args.userId != null -> { - val user = resolveUser(args.userId) + initialState.userId != null -> { + val user = resolveUser(initialState.userId) setState { copy( @@ -110,7 +108,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - else -> { + else -> { setState { copy( matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), @@ -122,24 +120,20 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveUser(userId: String): User { - return (session.getUser(userId) - ?: tryOrNull { - awaitCallback { - session.getProfile(userId, it) + return tryOrNull { + awaitCallback { + session.resolveUser(userId, it) } - }?.let { - User(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) } - // Create raw Uxid in case the user is not searchable - ?: User(userId, null, null)) + // Create raw user in case the user is not searchable + ?: User(userId, null, null) } companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - val args: MatrixToBottomSheet.MatrixToArgs = viewModelContext.args() - return fragment.matrixToBottomSheetViewModelFactory.create(state, args) + return fragment.matrixToBottomSheetViewModelFactory.create(state) } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 93b198f9d7..98acab147e 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -35,10 +35,8 @@ import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser -import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback @@ -147,15 +145,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } is PermalinkData.UserLink -> { - val user = session.getUser(linkedId.userId) - ?: tryOrNull { - awaitCallback { - session.getProfile(linkedId.userId, it) - } - }?.let { - User(linkedId.userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) - } - // Create raw Uxid in case the user is not searchable + val user = tryOrNull { + awaitCallback { + session.resolveUser(linkedId.userId, it) + } + } + // Create raw Uxid in case the user is not searchable ?: User(linkedId.userId, null, null) setState { From 4f5632b9163648aefd5edee7186e997d465856bc Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 18:02:01 +0100 Subject: [PATCH 3/4] code review --- .../vector/app/features/home/HomeActivity.kt | 12 +++---- .../matrixto/MatrixToBottomSheetViewModel.kt | 35 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index e3e942fce9..d0d10beb31 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -125,9 +125,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } @@ -144,9 +144,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) - HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() - is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } @@ -179,7 +179,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private fun renderState(state: HomeActivityViewState) { when (val status = state.initialSyncProgressServiceStatus) { - is InitialSyncProgressService.Status.Idle -> { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 5f06bc6edd..79af3684b9 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -121,11 +121,11 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private suspend fun resolveUser(userId: String): User { return tryOrNull { - awaitCallback { - session.resolveUser(userId, it) - } - } - // Create raw user in case the user is not searchable + awaitCallback { + session.resolveUser(userId, it) + } + } + // Create raw user in case the user is not searchable ?: User(userId, null, null) } @@ -166,19 +166,18 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - val roomId = - try { - awaitCallback { session.createRoom(roomParams, it) }.also { - setState { - copy(startChattingState = Success(Unit)) - } - } - } catch (failure: Throwable) { - setState { - copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) - } - return@launch - } + val roomId = try { + awaitCallback { session.createRoom(roomParams, it) } + } catch (failure: Throwable) { + setState { + copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) + } + return@launch + } + setState { + // we can hide this button has we will navigate out + copy(startChattingState = Uninitialized) + } _viewEvents.post(MatrixToViewEvents.NavigateToRoom(roomId)) } } From 835a36986da73ea41db70bc95f900ad3c9b4d7e8 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 26 Nov 2020 17:39:00 +0100 Subject: [PATCH 4/4] Refactoring following review --- .../vector/app/features/home/HomeActivity.kt | 5 +- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../features/matrixto/MatrixToBottomSheet.kt | 18 +----- .../matrixto/MatrixToBottomSheetState.kt | 4 +- .../matrixto/MatrixToBottomSheetViewModel.kt | 63 +++++++------------ .../features/permalink/PermalinkHandler.kt | 7 ++- .../app/features/usercode/UserCodeActivity.kt | 2 +- .../usercode/UserCodeSharedViewModel.kt | 2 +- .../app/features/usercode/UserCodeState.kt | 2 +- 9 files changed, 36 insertions(+), 69 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index d0d10beb31..7dde0edf32 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.view.MenuItem @@ -365,14 +366,14 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } - override fun navToMemberProfile(userId: String): Boolean { + override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { val listener = object : MatrixToBottomSheet.InteractionListener { override fun navigateToRoom(roomId: String) { navigator.openRoom(this@HomeActivity, roomId) } } // TODO check if there is already one?? - MatrixToBottomSheet.withUserId(userId, listener) + MatrixToBottomSheet.withLink(deepLink.toString(), listener) .show(supportFragmentManager, "HA#MatrixToBottomSheet") return true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index e289234e7a..3f5e476a5e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1460,7 +1460,7 @@ class RoomDetailFragment @Inject constructor( return false } - override fun navToMemberProfile(userId: String): Boolean { + override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { openRoomMemberProfile(userId) return true } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 41020ea404..69f30bb470 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -41,8 +41,7 @@ class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class MatrixToArgs( - val matrixToLink: String?, - val userId: String? + val matrixToLink: String ) : Parcelable @Inject lateinit var avatarRenderer: AvatarRenderer @@ -136,20 +135,7 @@ class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { return MatrixToBottomSheet().apply { arguments = Bundle().apply { putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( - matrixToLink = matrixToLink, - userId = null - )) - } - interactionListener = listener - } - } - - fun withUserId(userId: String, listener: InteractionListener?): MatrixToBottomSheet { - return MatrixToBottomSheet().apply { - arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( - matrixToLink = null, - userId = userId + matrixToLink = matrixToLink )) } interactionListener = listener diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 9ec2071a94..9b1ce9fea8 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -22,14 +22,12 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.util.MatrixItem data class MatrixToBottomSheetState( - val userId: String? = null, - val deepLink: String? = null, + val deepLink: String, val matrixItem: Async = Uninitialized, val startChattingState: Async = Uninitialized ) : MvRxState { constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( - userId = args.userId, deepLink = args.matrixToLink ) } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 79af3684b9..6e8a530c9a 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -65,42 +65,20 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveLink(initialState: MatrixToBottomSheetState) { - when { - initialState.deepLink != null -> { - val linkedId = PermalinkParser.parse(initialState.deepLink) - if (linkedId is PermalinkData.FallbackLink) { - setState { - copy( - matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), - startChattingState = Uninitialized - ) - } - return - } - - when (linkedId) { - is PermalinkData.UserLink -> { - val user = resolveUser(linkedId.userId) - setState { - copy( - matrixItem = Success(user.toMatrixItem()), - startChattingState = Success(Unit) - ) - } - } - is PermalinkData.RoomLink -> { - // not yet supported - } - is PermalinkData.GroupLink -> { - // not yet supported - } - is PermalinkData.FallbackLink -> { - } - } + val permalinkData = PermalinkParser.parse(initialState.deepLink) + if (permalinkData is PermalinkData.FallbackLink) { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), + startChattingState = Uninitialized + ) } - initialState.userId != null -> { - val user = resolveUser(initialState.userId) + return + } + when (permalinkData) { + is PermalinkData.UserLink -> { + val user = resolveUser(permalinkData.userId) setState { copy( matrixItem = Success(user.toMatrixItem()), @@ -108,13 +86,16 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - else -> { - setState { - copy( - matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), - startChattingState = Uninitialized - ) - } + is PermalinkData.RoomLink -> { + // not yet supported + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + is PermalinkData.GroupLink -> { + // not yet supported + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + is PermalinkData.FallbackLink -> { + _viewEvents.post(MatrixToViewEvents.Dismiss) } } } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index 11c55f6a73..f1149d8990 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -63,13 +63,14 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .flatMap { permalinkData -> - handlePermalink(permalinkData, context, navigationInterceptor, buildTask) + handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask) } .onErrorReturnItem(false) } private fun handlePermalink( permalinkData: PermalinkData, + rawLink: Uri, context: Context, navigationInterceptor: NavigationInterceptor?, buildTask: Boolean @@ -96,7 +97,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti Single.just(true) } is PermalinkData.UserLink -> { - if (navigationInterceptor?.navToMemberProfile(permalinkData.userId) != true) { + if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) { navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) } Single.just(true) @@ -175,7 +176,7 @@ interface NavigationInterceptor { /** * Return true if the navigation has been intercepted */ - fun navToMemberProfile(userId: String): Boolean { + fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { return false } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index d6279470ae..547e2d939f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -71,7 +71,7 @@ class UserCodeActivity UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) - MatrixToBottomSheet.withUserId(mode.matrixItem.id, this).show(supportFragmentManager, "MatrixToBottomSheet") + MatrixToBottomSheet.withLink(mode.rawLink, this).show(supportFragmentManager, "MatrixToBottomSheet") } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 98acab147e..45b6f0ee65 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -155,7 +155,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( setState { copy( - mode = UserCodeState.Mode.RESULT(user.toMatrixItem()) + mode = UserCodeState.Mode.RESULT(user.toMatrixItem(), action.code) ) } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt index fdcfa2cb5e..c26da7c0a4 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt @@ -28,7 +28,7 @@ data class UserCodeState( sealed class Mode { object SHOW : Mode() object SCAN : Mode() - data class RESULT(val matrixItem: MatrixItem) : Mode() + data class RESULT(val matrixItem: MatrixItem, val rawLink: String) : Mode() } constructor(args: UserCodeActivity.Args) : this(