Open Space Link initial commit

This commit is contained in:
Valere 2021-02-27 11:13:54 +01:00
parent 03ef480bea
commit ef42591534
18 changed files with 851 additions and 156 deletions

View File

@ -24,6 +24,7 @@ sealed class PeekResult {
val topic: String?,
val avatarUrl: String?,
val numJoinedMembers: Int?,
val roomType: String?,
val viaServers: List<String>
) : PeekResult()

View File

@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
@ -100,7 +101,8 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = publicRepoResult.name,
topic = publicRepoResult.topic,
numJoinedMembers = publicRepoResult.numJoinedMembers,
viaServers = serverList
viaServers = serverList,
roomType = null // would be nice to get that from directory...
)
}
@ -130,6 +132,10 @@ internal class DefaultPeekRoomTask @Inject constructor(
.distinctBy { it.stateKey }
.count()
val roomType = stateEvents
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
?.let { it.content?.toModel<RoomCreateContent>()?.type }
return PeekResult.Success(
roomId = roomId,
alias = alias,
@ -137,6 +143,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = name,
topic = topic,
numJoinedMembers = memberCount,
roomType = roomType,
viaServers = serverList
)
} catch (failure: Throwable) {

View File

@ -72,6 +72,8 @@ import im.vector.app.features.login.LoginSplashFragment
import im.vector.app.features.login.LoginWaitForEmailFragment
import im.vector.app.features.login.LoginWebFragment
import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment
import im.vector.app.features.matrixto.MatrixToUserFragment
import im.vector.app.features.pin.PinFragment
import im.vector.app.features.qrcode.QrCodeScannerFragment
import im.vector.app.features.reactions.EmojiChooserFragment
@ -654,4 +656,14 @@ interface FragmentModule {
@IntoMap
@FragmentKey(CreateSpaceDefaultRoomsFragment::class)
fun bindCreateSpaceDefaultRoomsFragment(fragment: CreateSpaceDefaultRoomsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(MatrixToUserFragment::class)
fun bindMatrixToUserFragment(fragment: MatrixToUserFragment): Fragment
@Binds
@IntoMap
@FragmentKey(MatrixToRoomSpaceFragment::class)
fun bindMatrixToRoomSpaceFragment(fragment: MatrixToRoomSpaceFragment): Fragment
}

View File

@ -451,6 +451,23 @@ class HomeActivity :
return true
}
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
if (roomId == null) return false
val listener = object : MatrixToBottomSheet.InteractionListener {
override fun navigateToRoom(roomId: String) {
navigator.openRoom(this@HomeActivity, roomId)
}
override fun switchToSpace(spaceId: String) {
navigator.switchToSpace(this@HomeActivity, spaceId, null, false)
}
}
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

@ -1445,7 +1445,7 @@ class RoomDetailFragment @Inject constructor(
override fun onUrlClicked(url: String, title: String): Boolean {
permalinkHandler
.launch(requireActivity(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
// Same room?
if (roomId == roomDetailArgs.roomId) {
// Navigation to same room
@ -1653,7 +1653,7 @@ class RoomDetailFragment @Inject constructor(
override fun onRoomCreateLinkClicked(url: String) {
permalinkHandler
.launch(requireContext(), url, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}

View File

@ -21,4 +21,11 @@ import org.matrix.android.sdk.api.util.MatrixItem
sealed class MatrixToAction : VectorViewModelAction {
data class StartChattingWithUser(val matrixItem: MatrixItem) : MatrixToAction()
object FailedToResolveUser : MatrixToAction()
object FailedToStartChatting : MatrixToAction()
data class JoinSpace(val spaceID: String, val viaServers: List<String>?) : MatrixToAction()
data class JoinRoom(val roomId: String, val viaServers: List<String>?) : MatrixToAction()
data class OpenSpace(val spaceID: String) : MatrixToAction()
data class OpenRoom(val roomId: String) : MatrixToAction()
// data class OpenSpace(val spaceID: String) : MatrixToAction()
}

View File

@ -21,22 +21,23 @@ import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isInvisible
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import androidx.fragment.app.Fragment
import com.airbnb.mvrx.Incomplete
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.extensions.commitTransaction
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetMatrixToCardBinding
import im.vector.app.features.home.AvatarRenderer
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import javax.inject.Inject
import kotlin.reflect.KClass
class MatrixToBottomSheet :
VectorBaseBottomSheetDialogFragment<BottomSheetMatrixToCardBinding>() {
@ -65,63 +66,41 @@ class MatrixToBottomSheet :
interface InteractionListener {
fun navigateToRoom(roomId: String)
fun switchToSpace(spaceId: String) {}
}
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
when (val item = state.matrixItem) {
Uninitialized -> {
views.matrixToCardContentLoading.isVisible = false
views.matrixToCardUserContentVisibility.isVisible = false
when (state.linkType) {
is PermalinkData.RoomLink -> {
views.matrixToCardContentLoading.isVisible = state.roomPeekResult is Incomplete
showFragment(MatrixToRoomSpaceFragment::class, Bundle())
}
is Loading -> {
views.matrixToCardContentLoading.isVisible = true
views.matrixToCardUserContentVisibility.isVisible = false
is PermalinkData.UserLink -> {
views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete
showFragment(MatrixToUserFragment::class, Bundle())
}
is Success -> {
views.matrixToCardContentLoading.isVisible = false
views.matrixToCardUserContentVisibility.isVisible = true
views.matrixToCardNameText.setTextOrHide(item.invoke().displayName)
views.matrixToCardUserIdText.setTextOrHide(item.invoke().id)
avatarRenderer.render(item.invoke(), views.matrixToCardAvatar)
is PermalinkData.GroupLink -> {
}
is Fail -> {
// TODO display some error copy?
dismiss()
is PermalinkData.FallbackLink -> {
}
}
}
when (state.startChattingState) {
Uninitialized -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = false
}
is Success -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = true
}
is Fail -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = true
// TODO display some error copy?
dismiss()
}
is Loading -> {
views.matrixToCardButtonLoading.isVisible = true
views.matrixToCardSendMessageButton.isInvisible = true
private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
childFragmentManager.commitTransaction {
replace(views.matrixToCardFragmentContainer.id,
fragmentClass.java,
bundle,
fragmentClass.simpleName
)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.matrixToCardSendMessageButton.debouncedClicks {
withState(viewModel) {
it.matrixItem.invoke()?.let { item ->
viewModel.handle(MatrixToAction.StartChattingWithUser(item))
}
}
}
viewModel.observeViewEvents {
when (it) {
@ -130,6 +109,16 @@ class MatrixToBottomSheet :
dismiss()
}
MatrixToViewEvents.Dismiss -> dismiss()
is MatrixToViewEvents.NavigateToSpace -> {
interactionListener?.switchToSpace(it.spaceId)
dismiss()
}
is MatrixToViewEvents.ShowModalError -> {
AlertDialog.Builder(requireContext())
.setMessage(it.error)
.setPositiveButton(getString(R.string.ok), null)
.show()
}
}
}
}

View File

@ -19,15 +19,45 @@ 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.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.util.MatrixItem
data class MatrixToBottomSheetState(
val deepLink: String,
val linkType: PermalinkData,
val matrixItem: Async<MatrixItem> = Uninitialized,
val startChattingState: Async<Unit> = Uninitialized
val startChattingState: Async<Unit> = Uninitialized,
val roomPeekResult: Async<RoomInfoResult> = Uninitialized
) : MvRxState {
constructor(args: MatrixToBottomSheet.MatrixToArgs) : this(
deepLink = args.matrixToLink
deepLink = args.matrixToLink,
linkType = PermalinkParser.parse(args.matrixToLink)
)
}
sealed class RoomInfoResult {
data class FullInfo(
val roomItem: MatrixItem.RoomItem,
val name: String,
val topic: String,
val memberCount: Int?,
val alias: String?,
val membership: Membership,
val roomType: String?,
val viaServers: List<String>?
) : RoomInfoResult()
data class PartialInfo(
val roomId: String?,
val viaServers: List<String>
) : RoomInfoResult()
data class UnknownAlias(
val alias: String?
) : RoomInfoResult()
object NotFound : RoomInfoResult()
}

View File

@ -25,9 +25,10 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
@ -35,19 +36,26 @@ import im.vector.app.features.createdirect.DirectRoomHelper
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.Membership
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
class MatrixToBottomSheetViewModel @AssistedInject constructor(
@Assisted initialState: MatrixToBottomSheetState,
private val session: Session,
private val stringProvider: StringProvider,
private val directRoomHelper: DirectRoomHelper,
private val rawService: RawService) : VectorViewModel<MatrixToBottomSheetState, MatrixToAction, MatrixToViewEvents>(initialState) {
private val errorFormatter: ErrorFormatter)
: VectorViewModel<MatrixToBottomSheetState, MatrixToAction, MatrixToViewEvents>(initialState) {
@AssistedFactory
interface Factory {
@ -55,8 +63,23 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
}
init {
setState {
copy(matrixItem = Loading())
when (initialState.linkType) {
is PermalinkData.RoomLink -> {
setState {
copy(roomPeekResult = Loading())
}
}
is PermalinkData.UserLink -> {
setState {
copy(matrixItem = Loading())
}
}
is PermalinkData.GroupLink -> {
// Not yet supported
}
is PermalinkData.FallbackLink -> {
// Not yet supported
}
}
viewModelScope.launch(Dispatchers.IO) {
resolveLink(initialState)
@ -64,7 +87,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
}
private suspend fun resolveLink(initialState: MatrixToBottomSheetState) {
val permalinkData = PermalinkParser.parse(initialState.deepLink)
val permalinkData = initialState.linkType
if (permalinkData is PermalinkData.FallbackLink) {
setState {
copy(
@ -75,8 +98,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
return
}
when (permalinkData) {
is PermalinkData.UserLink -> {
when (permalinkData) {
is PermalinkData.UserLink -> {
val user = resolveUser(permalinkData.userId)
setState {
copy(
@ -85,11 +108,79 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
)
}
}
is PermalinkData.RoomLink -> {
// not yet supported
_viewEvents.post(MatrixToViewEvents.Dismiss)
is PermalinkData.RoomLink -> {
// could this room be already known
val knownRoom = if (permalinkData.isRoomAlias) {
tryOrNull {
awaitCallback<Optional<RoomAliasDescription>> {
session.getRoomIdByAlias(permalinkData.roomIdOrAlias, false, it)
}
}
?.getOrNull()
?.roomId?.let {
session.getRoom(permalinkData.roomIdOrAlias)
}
} else {
session.getRoom(permalinkData.roomIdOrAlias)
}?.roomSummary()
if (knownRoom != null) {
setState {
copy(
roomPeekResult = Success(
RoomInfoResult.FullInfo(
roomItem = knownRoom.toMatrixItem(),
name = knownRoom.name,
topic = knownRoom.topic,
memberCount = knownRoom.joinedMembersCount,
alias = knownRoom.canonicalAlias,
membership = knownRoom.membership,
roomType = knownRoom.roomType,
viaServers = null
)
)
)
}
} else {
val result = when (val peekResult = tryOrNull { resolveRoom(permalinkData.roomIdOrAlias) }) {
is PeekResult.Success -> {
RoomInfoResult.FullInfo(
roomItem = MatrixItem.RoomItem(peekResult.roomId, peekResult.name, peekResult.avatarUrl),
name = peekResult.name ?: "",
topic = peekResult.topic ?: "",
memberCount = peekResult.numJoinedMembers,
alias = peekResult.alias,
membership = Membership.NONE,
roomType = peekResult.roomType,
viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters
)
}
is PeekResult.PeekingNotAllowed -> {
RoomInfoResult.PartialInfo(
roomId = permalinkData.roomIdOrAlias,
viaServers = permalinkData.viaParameters
)
}
PeekResult.UnknownAlias -> {
RoomInfoResult.NotFound
}
null -> {
RoomInfoResult.PartialInfo(
roomId = permalinkData.roomIdOrAlias,
viaServers = permalinkData.viaParameters
).takeIf { permalinkData.isRoomAlias.not() }
?: RoomInfoResult.NotFound
}
}
setState {
copy(
roomPeekResult = Success(result)
)
}
}
}
is PermalinkData.GroupLink -> {
is PermalinkData.GroupLink -> {
// not yet supported
_viewEvents.post(MatrixToViewEvents.Dismiss)
}
@ -105,6 +196,19 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
?: User(userId, null, null)
}
/**
* Let's try to get some information about that room,
* main thing is trying to see if it's a space or a room
*/
private suspend fun resolveRoom(roomIdOrAlias: String): PeekResult {
return tryOrNull { // this should not throw as it returns a result, but better be safe
awaitCallback {
session.peekRoom(roomIdOrAlias, it)
}
}
?: PeekResult.PeekingNotAllowed(roomIdOrAlias, null, emptyList())
}
companion object : MvRxViewModelFactory<MatrixToBottomSheetViewModel, MatrixToBottomSheetState> {
override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? {
val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
@ -116,14 +220,75 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
override fun handle(action: MatrixToAction) {
when (action) {
is MatrixToAction.StartChattingWithUser -> handleStartChatting(action)
MatrixToAction.FailedToResolveUser -> {
_viewEvents.post(MatrixToViewEvents.Dismiss)
}
MatrixToAction.FailedToStartChatting -> {
_viewEvents.post(MatrixToViewEvents.Dismiss)
}
is MatrixToAction.JoinSpace -> handleJoinSpace(action)
is MatrixToAction.JoinRoom -> handleJoinRoom(action)
is MatrixToAction.OpenSpace -> {
_viewEvents.post(MatrixToViewEvents.NavigateToSpace(action.spaceID))
}
is MatrixToAction.OpenRoom -> {
_viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId))
}
}.exhaustive
}
private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) {
private fun handleJoinSpace(joinSpace: MatrixToAction.JoinSpace) {
setState {
copy(startChattingState = Loading())
}
viewModelScope.launch {
setState {
copy(startChattingState = Loading())
try {
val joinResult = session.spaceService().joinSpace(joinSpace.spaceID, null, joinSpace.viaServers?.take(3) ?: emptyList())
if (joinResult.isSuccess()) {
_viewEvents.post(MatrixToViewEvents.NavigateToSpace(joinSpace.spaceID))
} else {
val errMsg = errorFormatter.toHumanReadable((joinResult as? SpaceService.JoinSpaceResult.Fail)?.error)
_viewEvents.post(MatrixToViewEvents.ShowModalError(errMsg))
}
} catch (failure: Throwable) {
_viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure)))
} finally {
setState {
// we can hide this button has we will navigate out
copy(startChattingState = Uninitialized)
}
}
}
}
private fun handleJoinRoom(action: MatrixToAction.JoinRoom) {
setState {
copy(startChattingState = Loading())
}
viewModelScope.launch {
try {
awaitCallback<Unit> {
session.joinRoom(action.roomId, null, action.viaServers?.take(3) ?: emptyList(), it)
}
_viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId))
} catch (failure: Throwable) {
_viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure)))
} finally {
setState {
// we can hide this button has we will navigate out
copy(startChattingState = Uninitialized)
}
}
}
}
private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) {
setState {
copy(startChattingState = Loading())
}
viewModelScope.launch {
val roomId = try {
directRoomHelper.ensureDMExists(action.matrixItem.id)
} catch (failure: Throwable) {

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.matrixto
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomType
import javax.inject.Inject
class MatrixToRoomSpaceFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentMatrixToRoomSpaceCardBinding>() {
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMatrixToRoomSpaceCardBinding {
return FragmentMatrixToRoomSpaceCardBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.matrixToCardMainButton.debouncedClicks {
mainButtonClicked()
}
views.matrixToCardSecondaryButton.debouncedClicks {
secondaryButtonClicked()
}
}
override fun invalidate() = withState(sharedViewModel) { state ->
when (val item = state.roomPeekResult) {
Uninitialized -> {
views.matrixToCardContentVisibility.isVisible = false
}
is Loading -> {
views.matrixToCardContentVisibility.isVisible = false
}
is Success -> {
views.matrixToCardContentVisibility.isVisible = true
when (val peek = item.invoke()) {
is RoomInfoResult.FullInfo -> {
val matrixItem = peek.roomItem
if (peek.roomType == RoomType.SPACE) {
avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar)
} else {
avatarRenderer.render(matrixItem, views.matrixToCardAvatar)
}
views.matrixToCardNameText.setTextOrHide(peek.name)
views.matrixToCardAliasText.setTextOrHide(peek.alias)
views.matrixToCardDescText.setTextOrHide(peek.topic)
val memberCount = peek.memberCount
if (memberCount != null) {
views.matrixToMemberPills.isVisible = true
views.spaceChildMemberCountText.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
} else {
// hide the pill
views.matrixToMemberPills.isVisible = false
}
when (peek.membership) {
Membership.LEAVE,
Membership.NONE -> {
views.matrixToCardMainButton.isVisible = true
views.matrixToCardMainButton.text = getString(R.string.join_space)
views.matrixToCardSecondaryButton.isVisible = false
}
Membership.INVITE -> {
views.matrixToCardMainButton.isVisible = true
views.matrixToCardSecondaryButton.isVisible = true
views.matrixToCardMainButton.text = getString(R.string.join_space)
views.matrixToCardSecondaryButton.text = getString(R.string.decline)
}
Membership.JOIN -> {
views.matrixToCardMainButton.isVisible = true
views.matrixToCardSecondaryButton.isVisible = false
views.matrixToCardMainButton.text = getString(R.string.action_open)
}
Membership.KNOCK,
Membership.BAN -> {
// What to do here ?
views.matrixToCardMainButton.isVisible = false
views.matrixToCardSecondaryButton.isVisible = false
}
}
}
is RoomInfoResult.PartialInfo -> {
// It may still be possible to join
views.matrixToCardNameText.text = peek.roomId
views.matrixToCardAliasText.isVisible = false
views.matrixToMemberPills.isVisible = false
views.matrixToCardDescText.setTextOrHide(getString(R.string.room_preview_no_preview))
views.matrixToCardMainButton.text = getString(R.string.join_anyway)
views.matrixToCardSecondaryButton.isVisible = false
}
RoomInfoResult.NotFound -> {
// we cannot join :/
views.matrixToCardNameText.isVisible = false
views.matrixToCardAliasText.isVisible = false
views.matrixToMemberPills.isVisible = false
views.matrixToCardDescText.setTextOrHide(getString(R.string.room_preview_not_found))
views.matrixToCardMainButton.isVisible = false
views.matrixToCardSecondaryButton.isVisible = false
}
is RoomInfoResult.UnknownAlias -> {
views.matrixToCardNameText.isVisible = false
views.matrixToCardAliasText.isVisible = false
views.spaceChildMemberCountText.isVisible = false
views.matrixToCardDescText.setTextOrHide(getString(R.string.room_alias_preview_not_found))
views.matrixToCardMainButton.isVisible = false
views.matrixToCardSecondaryButton.isVisible = false
}
}
}
is Fail -> {
// TODO display some error copy?
sharedViewModel.handle(MatrixToAction.FailedToResolveUser)
}
}
when (state.startChattingState) {
Uninitialized -> {
views.matrixToCardButtonLoading.isVisible = false
// views.matrixToCardMainButton.isVisible = false
}
is Success -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardMainButton.isVisible = true
}
is Fail -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardMainButton.isVisible = true
// TODO display some error copy?
}
is Loading -> {
views.matrixToCardButtonLoading.isVisible = true
views.matrixToCardMainButton.isInvisible = true
}
}
}
private fun mainButtonClicked() = withState(sharedViewModel) { state ->
when (val info = state.roomPeekResult.invoke()) {
is RoomInfoResult.FullInfo -> {
when (info.membership) {
Membership.NONE,
Membership.INVITE,
Membership.LEAVE -> {
if (info.roomType == RoomType.SPACE) {
sharedViewModel.handle(MatrixToAction.JoinSpace(info.roomItem.id, info.viaServers))
} else {
sharedViewModel.handle(MatrixToAction.JoinRoom(info.roomItem.id, info.viaServers))
}
}
Membership.JOIN -> {
if (info.roomType == RoomType.SPACE) {
sharedViewModel.handle(MatrixToAction.OpenSpace(info.roomItem.id))
} else {
sharedViewModel.handle(MatrixToAction.OpenRoom(info.roomItem.id))
}
}
else -> {
}
}
}
is RoomInfoResult.PartialInfo -> {
}
else -> {
}
}
}
fun secondaryButtonClicked() = withState(sharedViewModel) { state ->
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.matrixto
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentMatrixToUserCardBinding
import im.vector.app.features.home.AvatarRenderer
import javax.inject.Inject
class MatrixToUserFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentMatrixToUserCardBinding>() {
private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMatrixToUserCardBinding {
return FragmentMatrixToUserCardBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.matrixToCardSendMessageButton.debouncedClicks {
withState(sharedViewModel) {
it.matrixItem.invoke()?.let { item ->
sharedViewModel.handle(MatrixToAction.StartChattingWithUser(item))
}
}
}
}
override fun invalidate() = withState(sharedViewModel) { state ->
when (val item = state.matrixItem) {
Uninitialized -> {
views.matrixToCardUserContentVisibility.isVisible = false
}
is Loading -> {
views.matrixToCardUserContentVisibility.isVisible = false
}
is Success -> {
views.matrixToCardUserContentVisibility.isVisible = true
views.matrixToCardNameText.setTextOrHide(item.invoke().displayName)
views.matrixToCardUserIdText.setTextOrHide(item.invoke().id)
avatarRenderer.render(item.invoke(), views.matrixToCardAvatar)
}
is Fail -> {
// TODO display some error copy?
sharedViewModel.handle(MatrixToAction.FailedToResolveUser)
}
}
when (state.startChattingState) {
Uninitialized -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = false
}
is Success -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = true
}
is Fail -> {
views.matrixToCardButtonLoading.isVisible = false
views.matrixToCardSendMessageButton.isVisible = true
// TODO display some error copy?
sharedViewModel.handle(MatrixToAction.FailedToStartChatting)
}
is Loading -> {
views.matrixToCardButtonLoading.isVisible = true
views.matrixToCardSendMessageButton.isInvisible = true
}
}
}
}

View File

@ -20,5 +20,7 @@ import im.vector.app.core.platform.VectorViewEvents
sealed class MatrixToViewEvents : VectorViewEvents {
data class NavigateToRoom(val roomId: String) : MatrixToViewEvents()
data class NavigateToSpace(val spaceId: String) : MatrixToViewEvents()
data class ShowModalError(val error: String) : MatrixToViewEvents()
object Dismiss : MatrixToViewEvents()
}

View File

@ -78,12 +78,12 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
buildTask: Boolean
): Single<Boolean> {
return when (permalinkData) {
is PermalinkData.RoomLink -> {
is PermalinkData.RoomLink -> {
permalinkData.getRoomId()
.observeOn(AndroidSchedulers.mainThread())
.map {
val roomId = it.getOrNull()
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) {
if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) {
openRoom(
context = context,
roomId = roomId,
@ -94,11 +94,11 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
true
}
}
is PermalinkData.GroupLink -> {
is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
Single.just(true)
}
is PermalinkData.UserLink -> {
is PermalinkData.UserLink -> {
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
@ -196,7 +196,7 @@ interface NavigationInterceptor {
/**
* Return true if the navigation has been intercepted
*/
fun navToRoom(roomId: String?, eventId: String? = null): Boolean {
fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri? = null): Boolean {
return false
}

View File

@ -16,6 +16,7 @@
package im.vector.app.features.roomdirectory
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
@ -127,7 +128,7 @@ class PublicRoomsFragment @Inject constructor(
val permalink = session.permalinkService().createPermalink(roomIdOrAlias)
permalinkHandler
.launch(requireContext(), permalink, object : NavigationInterceptor {
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean {
requireActivity().finish()
return false
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -10,88 +9,13 @@
android:id="@+id/matrixToCardContentLoading"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerInParent="true"
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_marginTop="@dimen/layout_vertical_margin_big"
android:contentDescription="@string/avatar"
android:elevation="4dp"
android:transitionName="profile"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/matrixToCardNameText"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/matrixToCardFragmentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@+id/matrixToCardAvatar"
tools:text="@sample/matrix.json/data/displayName" />
android:layout_height="wrap_content" />
<TextView
android:id="@+id/matrixToCardUserIdText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@id/matrixToCardNameText"
tools:text="@sample/matrix.json/data/mxid" />
<com.google.android.material.button.MaterialButton
android:id="@+id/matrixToCardSendMessageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
android:minWidth="130dp"
android:text="@string/start_chatting"
app:icon="@drawable/ic_fab_add_chat"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
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>
</RelativeLayout>

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:minHeight="200dp"
android:padding="16dp">
<ImageView
android:id="@+id/matrixToCardAvatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:contentDescription="@string/avatar"
android:elevation="4dp"
android:transitionName="profile"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/matrixToCardNameText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/matrixToCardAvatar"
tools:text="@sample/matrix.json/data/roomName" />
<TextView
android:id="@+id/matrixToCardAliasText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@id/matrixToCardNameText"
tools:text="@sample/matrix.json/data/roomAlias" />
<LinearLayout
android:id="@+id/matrixToMemberPills"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/pill_receipt"
android:gravity="center"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingTop="8dp"
android:paddingEnd="12dp"
android:paddingBottom="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardAliasText">
<ImageView
android:id="@+id/spaceChildMemberCountIcon"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="4dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_room_profile_member_list"
app:tint="?riotx_text_primary" />
<TextView
android:id="@+id/spaceChildMemberCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:textColor="?riotx_text_primary"
tools:text="123 members" />
</LinearLayout>
<TextView
android:id="@+id/matrixToCardDescText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:maxLines="4"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@id/matrixToMemberPills"
tools:text="@sample/matrix.json/data/roomTopic" />
<com.google.android.material.button.MaterialButton
android:id="@+id/matrixToCardMainButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardDescText"
app:layout_constraintWidth_max="400dp"
tools:text="@string/join" />
<com.google.android.material.button.MaterialButton
android:id="@+id/matrixToCardSecondaryButton"
style="@style/VectorButtonStyleOutlined"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
android:textAllCaps="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/matrixToCardMainButton"
app:layout_constraintWidth_max="400dp"
tools:text="@string/dismiss" />
<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/matrixToCardMainButton"
app:layout_constraintEnd_toEndOf="@id/matrixToCardMainButton"
app:layout_constraintStart_toStartOf="@id/matrixToCardMainButton"
app:layout_constraintTop_toTopOf="@id/matrixToCardMainButton"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group
android:id="@+id/matrixToCardContentVisibility"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="matrixToCardNameText,matrixToCardAvatar,matrixToCardNameText,matrixToCardDescText, matrixToCardMainButton, matrixToCardAliasText, matrixToCardSecondaryButton, matrixToMemberPills"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:minHeight="200dp">
<ImageView
android:id="@+id/matrixToCardAvatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:contentDescription="@string/avatar"
android:elevation="4dp"
android:transitionName="profile"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/matrixToCardNameText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/layout_vertical_margin_big"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@+id/matrixToCardAvatar"
tools:text="@sample/matrix.json/data/displayName" />
<TextView
android:id="@+id/matrixToCardUserIdText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:singleLine="true"
android:textAlignment="center"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@id/matrixToCardNameText"
tools:text="@sample/matrix.json/data/mxid" />
<com.google.android.material.button.MaterialButton
android:id="@+id/matrixToCardSendMessageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="@dimen/layout_vertical_margin_big"
android:minWidth="130dp"
android:text="@string/start_chatting"
app:icon="@drawable/ic_fab_add_chat"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
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>

View File

@ -3286,6 +3286,9 @@
<!-- First one is the space name, and the second one is the matrix.to link -->
<string name="share_space_link_message">Join my space %1$s %2$s</string>
<string name="skip_for_now">Skip for now</string>
<string name="join_space">Join Space</string>
<string name="join_anyway">Join Anyway</string>
<string name="room_alias_preview_not_found">This alias is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string>
</resources>