M matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt => matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +5 -0
@@ 36,6 36,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
* @param limit the max number of users to return
M matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt => matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +31 -0
@@ 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)
}
M vector/src/main/AndroidManifest.xml => vector/src/main/AndroidManifest.xml +3 -3
@@ 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" />
M vector/src/main/java/im/vector/app/features/home/HomeActivity.kt => vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +47 -3
@@ 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(
M vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt => vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +1 -1
@@ 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
}
A vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt => vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt +24 -0
@@ 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()
+}
M vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt => vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +92 -12
@@ 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
}
}
A vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt => vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +33 -0
@@ 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
+ )
+}
A vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt => vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +166 -0
@@ 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))
+ }
+ }
+ }
+}
A vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt => vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt +24 -0
@@ 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()
+}
M vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt => vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +4 -3
@@ 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
}
}
M vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt => vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt +17 -14
@@ 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() {
M vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt => vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +3 -4
@@ 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) {
M vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt => vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +26 -14
@@ 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)
}
M vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt => vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt +1 -1
@@ 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(
M vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml => vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml +32 -2
@@ 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>