Merge pull request #2444 from vector-im/feature/bca/deeplink_mxto

Fix issues with matrix.to deep linking
This commit is contained in:
Benoit Marty 2020-11-27 10:22:51 +01:00 committed by GitHub
commit bc889cbcf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 509 additions and 57 deletions

View File

@ -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<User>)
/**
* Search list of users on server directory.
* @param search the searched term

View File

@ -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<User>) {
val known = getUser(userId)
if (known != null) {
callback.onSuccess(known)
} else {
val params = GetProfileInfoTask.Params(userId)
getProfileInfoTask
.configureWith(params) {
this.callback = object : MatrixCallback<JsonDict> {
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<Optional<User>> {
return userDataSource.getUserLive(userId)
}

View File

@ -81,7 +81,8 @@
android:resource="@xml/shortcuts" />
</activity-alias>
<activity android:name=".features.home.HomeActivity" />
<activity android:name=".features.home.HomeActivity"
android:launchMode="singleTask"/>
<activity
android:name=".features.login.LoginActivity"
android:launchMode="singleTask"
@ -189,10 +190,9 @@
<activity
android:name=".features.signout.soft.SoftLogoutActivity"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".features.permalink.PermalinkHandlerActivity">
<activity android:name=".features.permalink.PermalinkHandlerActivity" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

View File

@ -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
@ -38,8 +39,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 +55,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 +71,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 +90,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) {
@ -145,6 +154,28 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
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) {
@ -270,6 +301,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
if (intent?.getParcelableExtra<HomeActivityArgs>(MvRx.KEY_ARG)?.clearNotification == true) {
notificationDrawerManager.clearAllEvents()
}
handleIntent(intent)
}
override fun onDestroy() {
@ -313,11 +345,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 +366,18 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
}
}
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.withLink(deepLink.toString(), listener)
.show(supportFragmentManager, "HA#MatrixToBottomSheet")
return true
}
companion object {
fun newIntent(context: Context, clearNotification: Boolean = false, accountCreation: Boolean = false): Intent {
val args = HomeActivityArgs(

View File

@ -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
}

View File

@ -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()
}

View File

@ -17,23 +17,37 @@
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
) : 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 +57,87 @@ 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
))
}
interactionListener = listener
}
}

View File

@ -0,0 +1,33 @@
/*
* 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 deepLink: String,
val matrixItem: Async<MatrixItem> = Uninitialized,
val startChattingState: Async<Unit> = Uninitialized
) : MvRxState {
constructor(args: MatrixToBottomSheet.MatrixToArgs) : this(
deepLink = args.matrixToLink
)
}

View File

@ -0,0 +1,166 @@
/*
* 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.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.util.awaitCallback
class MatrixToBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: MatrixToBottomSheetState,
private val session: Session,
private val stringProvider: StringProvider,
private val rawService: RawService) : VectorViewModel<MatrixToBottomSheetState, MatrixToAction, MatrixToViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: MatrixToBottomSheetState): MatrixToBottomSheetViewModel
}
init {
setState {
copy(matrixItem = Loading())
}
viewModelScope.launch(Dispatchers.IO) {
resolveLink(initialState)
}
}
private suspend fun resolveLink(initialState: MatrixToBottomSheetState) {
val permalinkData = PermalinkParser.parse(initialState.deepLink)
if (permalinkData is PermalinkData.FallbackLink) {
setState {
copy(
matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))),
startChattingState = Uninitialized
)
}
return
}
when (permalinkData) {
is PermalinkData.UserLink -> {
val user = resolveUser(permalinkData.userId)
setState {
copy(
matrixItem = Success(user.toMatrixItem()),
startChattingState = Success(Unit)
)
}
}
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)
}
}
}
private suspend fun resolveUser(userId: String): User {
return tryOrNull {
awaitCallback<User> {
session.resolveUser(userId, it)
}
}
// Create raw user in case the user is not searchable
?: User(userId, null, null)
}
companion object : MvRxViewModelFactory<MatrixToBottomSheetViewModel, MatrixToBottomSheetState> {
override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? {
val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.matrixToBottomSheetViewModelFactory.create(state)
}
}
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<String> { 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))
}
}
}
}

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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.withLink(mode.rawLink, 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) {

View File

@ -30,6 +30,7 @@ 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
@ -72,12 +73,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,22 +140,33 @@ 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<List<User>> {
session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it)
}.firstOrNull { it.userId == linkedId.userId }
is PermalinkData.RoomLink -> {
// not yet supported
_viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented)))
}
is PermalinkData.UserLink -> {
val user = tryOrNull {
awaitCallback<User> {
session.resolveUser(linkedId.userId, it)
}
}
// Create raw Uxid in case the user is not searchable
?: User(linkedId.userId, null, null)
?: User(linkedId.userId, null, null)
setState {
copy(
mode = UserCodeState.Mode.RESULT(user.toMatrixItem())
mode = UserCodeState.Mode.RESULT(user.toMatrixItem(), action.code)
)
}
}
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)
}

View File

@ -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(

View File

@ -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">
<ProgressBar
android:id="@+id/matrixToCardContentLoading"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/matrixToCardAvatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:elevation="4dp"
android:transitionName="profile"
@ -63,4 +74,23 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardUserIdText" />
<ProgressBar
android:id="@+id/matrixToCardButtonLoading"
style="?android:attr/progressBarStyleSmall"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/matrixToCardSendMessageButton"
app:layout_constraintEnd_toEndOf="@id/matrixToCardSendMessageButton"
app:layout_constraintStart_toStartOf="@id/matrixToCardSendMessageButton"
app:layout_constraintTop_toTopOf="@id/matrixToCardSendMessageButton"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/matrixToCardUserContentVisibility"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="matrixToCardAvatar,matrixToCardNameText,matrixToCardUserIdText"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>