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">
+
+
+
+
+
+