diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 1bde7c5b28..190623c9b6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -66,6 +66,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -191,7 +192,7 @@ class LoggedInFlowNode @AssistedInject constructor( data class Room( val roomId: RoomId, val roomDescription: RoomDescription? = null, - val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages + val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(null) ) : NavTarget @Parcelize @@ -270,6 +271,40 @@ class LoggedInFlowNode @AssistedInject constructor( coroutineScope.launch { attachRoom(roomId) } } + override fun onPermalinkClicked(data: PermalinkData) { + coroutineScope.launch { + when (data) { + is PermalinkData.UserLink -> { + // FIXME: Add a user profile screen. + Timber.e("User link clicked: ${data.userId}. TODO Add a user profile screen") + } + is PermalinkData.RoomIdLink -> { + backstack.push(NavTarget.Room(data.roomId)) + } + is PermalinkData.RoomAliasLink -> { + // FIXME Implement room alias navigation + Timber.e("Room alias link clicked: ${data.roomAlias}. TODO Handle a room alias navigation") + } + is PermalinkData.EventIdAliasLink -> { + // FIXME Implement event alias navigation + Timber.e("Event with room alias link clicked: ${data.eventId}. TODO Handle an event with room alias navigation") + } + is PermalinkData.EventIdLink -> { + backstack.push( + NavTarget.Room( + data.roomId, + initialElement = RoomNavigationTarget.Messages(data.eventId) + ) + ) + } + is PermalinkData.FallbackLink, + is PermalinkData.RoomEmailInviteLink -> { + // Should not happen (handled by MessagesNode) + } + } + } + } + override fun onOpenGlobalNotificationSettings() { backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings)) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index db3664e631..2f62045b22 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -75,7 +75,7 @@ class RoomFlowNode @AssistedInject constructor( data class Inputs( val roomId: RoomId, val roomDescription: Optional, - val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, + val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(), ) : NodeInputs private val inputs: Inputs = inputs() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt index 901b2667be..776171d141 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt @@ -16,8 +16,17 @@ package io.element.android.appnav.room -enum class RoomNavigationTarget { - Messages, - Details, - NotificationSettings, +import android.os.Parcelable +import io.element.android.libraries.matrix.api.core.EventId +import kotlinx.parcelize.Parcelize + +sealed interface RoomNavigationTarget : Parcelable { + @Parcelize + data class Messages(val eventId: EventId? = null) : RoomNavigationTarget + + @Parcelize + data object Details : RoomNavigationTarget + + @Parcelize + data object NotificationSettings : RoomNavigationTarget } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index 36def888ac..ae8d03be33 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -69,7 +69,7 @@ class JoinedRoomFlowNode @AssistedInject constructor( ) { data class Inputs( val roomId: RoomId, - val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, + val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(), ) : NodeInputs private val inputs: Inputs = inputs() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index a5d7893c91..7a6c736385 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -42,8 +42,10 @@ import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -63,8 +65,8 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( roomComponentFactory: RoomComponentFactory, ) : BaseFlowNode( backstack = BackStack( - initialElement = when (plugins.filterIsInstance(Inputs::class.java).first().initialElement) { - RoomNavigationTarget.Messages -> NavTarget.Messages + initialElement = when (val input = plugins.filterIsInstance(Inputs::class.java).first().initialElement) { + is RoomNavigationTarget.Messages -> NavTarget.Messages(input.eventId) RoomNavigationTarget.Details -> NavTarget.RoomDetails RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings }, @@ -75,13 +77,14 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( ), DaggerComponentOwner { interface Callback : Plugin { fun onOpenRoom(roomId: RoomId) + fun onPermalinkClicked(data: PermalinkData) fun onForwardedToSingleRoom(roomId: RoomId) fun onOpenGlobalNotificationSettings() } data class Inputs( val room: MatrixRoom, - val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, + val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(), ) : NodeInputs private val inputs: Inputs = inputs() @@ -139,7 +142,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Messages -> { + is NavTarget.Messages -> { val callback = object : MessagesEntryPoint.Callback { override fun onRoomDetailsClicked() { backstack.push(NavTarget.RoomDetails) @@ -149,11 +152,18 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( backstack.push(NavTarget.RoomMemberDetails(userId)) } + override fun onPermalinkClicked(data: PermalinkData) { + callbacks.forEach { it.onPermalinkClicked(data) } + } + override fun onForwardedToSingleRoom(roomId: RoomId) { callbacks.forEach { it.onForwardedToSingleRoom(roomId) } } } - messagesEntryPoint.createNode(this, buildContext, callback) + messagesEntryPoint.nodeBuilder(this, buildContext) + .params(MessagesEntryPoint.Params(navTarget.eventId)) + .callback(callback) + .build() } NavTarget.RoomDetails -> { createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomDetails) @@ -169,7 +179,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - data object Messages : NavTarget + data class Messages(val eventId: EventId? = null) : NavTarget @Parcelize data object RoomDetails : NavTarget diff --git a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt index 08702eeb5a..2809fc95ee 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt @@ -47,14 +47,30 @@ class JoinRoomLoadedFlowNodeTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() - private class FakeMessagesEntryPoint : MessagesEntryPoint { + private class FakeMessagesEntryPoint : MessagesEntryPoint, MessagesEntryPoint.NodeBuilder { + var buildContext: BuildContext? = null var nodeId: String? = null + var parameters: MessagesEntryPoint.Params? = null var callback: MessagesEntryPoint.Callback? = null - override fun createNode(parentNode: Node, buildContext: BuildContext, callback: MessagesEntryPoint.Callback): Node { - return node(buildContext) {}.also { + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder { + this.buildContext = buildContext + return this + } + + override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder { + parameters = params + return this + } + + override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder { + this.callback = callback + return this + } + + override fun build(): Node { + return node(buildContext!!) {}.also { nodeId = it.id - this.callback = callback } } } @@ -118,9 +134,9 @@ class JoinRoomLoadedFlowNodeTest { val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper() // THEN - assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages) - roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages, Lifecycle.State.CREATED) - val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages)!! + assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages()) + roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages(), Lifecycle.State.CREATED) + val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages())!! assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index bdef36d6a8..f088603d3b 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState +import androidx.compose.runtime.rememberCoroutineScope import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents @@ -32,6 +33,8 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo +import io.element.android.libraries.matrix.api.room.preview.RoomPreview +import kotlinx.coroutines.launch import java.util.Optional class JoinRoomPresenter @AssistedInject constructor( @@ -46,6 +49,7 @@ class JoinRoomPresenter @AssistedInject constructor( @Composable override fun present(): JoinRoomState { + val coroutineScope = rememberCoroutineScope() val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty()) val contentState by produceState(initialValue = ContentState.Loading(roomId), key1 = roomInfo) { value = when { @@ -56,6 +60,12 @@ class JoinRoomPresenter @AssistedInject constructor( roomDescription.get().toContentState() } else -> { + coroutineScope.launch { + val result = matrixClient.getRoomPreview(roomId.value) + value = result.getOrNull() + ?.toContentState() + ?: ContentState.UnknownRoom(roomId) + } ContentState.Loading(roomId) } } @@ -64,7 +74,8 @@ class JoinRoomPresenter @AssistedInject constructor( fun handleEvents(event: JoinRoomEvents) { when (event) { - JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> { + JoinRoomEvents.AcceptInvite, + JoinRoomEvents.JoinRoom -> { val inviteData = contentState.toInviteData() ?: return acceptDeclineInviteState.eventSink( AcceptDeclineInviteEvents.AcceptInvite(inviteData) @@ -87,6 +98,24 @@ class JoinRoomPresenter @AssistedInject constructor( } } +private fun RoomPreview.toContentState(): ContentState { + return ContentState.Loaded( + roomId = roomId, + name = name, + topic = topic, + alias = canonicalAlias, + numberOfMembers = numberOfJoinedMembers, + isDirect = false, + roomAvatarUrl = avatarUrl, + joinAuthorisationStatus = when { + isInvited -> JoinAuthorisationStatus.IsInvited + canKnock -> JoinAuthorisationStatus.CanKnock + isPublic -> JoinAuthorisationStatus.CanJoin + else -> JoinAuthorisationStatus.Unknown + } + ) +} + @VisibleForTesting internal fun RoomDescription.toContentState(): ContentState { return ContentState.Loaded( diff --git a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt index 482dfad8ea..9012d3a776 100644 --- a/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt +++ b/features/messages/api/src/main/kotlin/io/element/android/features/messages/api/MessagesEntryPoint.kt @@ -20,19 +20,28 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkData interface MessagesEntryPoint : FeatureEntryPoint { - fun createNode( - parentNode: Node, - buildContext: BuildContext, - callback: Callback, - ): Node + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun params(params: Params): NodeBuilder + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + data class Params( + val eventId: EventId?, + ) interface Callback : Plugin { fun onRoomDetailsClicked() fun onUserDataClicked(userId: UserId) + fun onPermalinkClicked(data: PermalinkData) fun onForwardedToSingleRoom(roomId: RoomId) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt index abf451b4b6..b45c7fc6b5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.libraries.architecture.createNode @@ -26,11 +27,23 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint { - override fun createNode( - parentNode: Node, - buildContext: BuildContext, - callback: MessagesEntryPoint.Callback - ): Node { - return parentNode.createNode(buildContext, listOf(callback)) + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : MessagesEntryPoint.NodeBuilder { + override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder { + plugins += MessagesNode.Inputs(eventId = params.eventId) + return this + } + + override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 3cf480f7d1..c8a0a753c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -52,6 +52,7 @@ import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.features.poll.api.create.CreatePollMode import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.operation.show @@ -62,6 +63,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.mediaviewer.api.local.MediaInfo import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode @@ -79,7 +81,7 @@ class MessagesFlowNode @AssistedInject constructor( private val createPollEntryPoint: CreatePollEntryPoint, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.Messages, + initialElement = NavTarget.Messages(plugins.filterIsInstance().firstOrNull()?.eventId), savedStateMap = buildContext.savedStateMap, ), overlay = Overlay( @@ -88,12 +90,16 @@ class MessagesFlowNode @AssistedInject constructor( buildContext = buildContext, plugins = plugins ) { + data class Inputs(val eventId: EventId?) : NodeInputs + sealed interface NavTarget : Parcelable { @Parcelize data object Empty : NavTarget @Parcelize - data object Messages : NavTarget + data class Messages( + val eventId: EventId? = null, + ) : NavTarget @Parcelize data class MediaViewer( @@ -149,6 +155,10 @@ class MessagesFlowNode @AssistedInject constructor( callback?.onUserDataClicked(userId) } + override fun onPermalinkClicked(data: PermalinkData) { + callback?.onPermalinkClicked(data) + } + override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) } @@ -181,7 +191,10 @@ class MessagesFlowNode @AssistedInject constructor( ElementCallActivity.start(context, inputs) } } - createNode(buildContext, listOf(callback)) + val params = MessagesNode.Inputs( + eventId = navTarget.eventId, + ) + createNode(buildContext, listOf(callback, params)) } is NavTarget.MediaViewer -> { val inputs = MediaViewerNode.Inputs( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index ff8c727f9c..0a4d1f039d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -34,6 +34,7 @@ import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPr import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.androidutils.system.openUrlInExternalApp +import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId @@ -42,6 +43,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.services.analytics.api.AnalyticsService @@ -62,11 +64,15 @@ class MessagesNode @AssistedInject constructor( private val presenter = presenterFactory.create(this) private val callback = plugins().firstOrNull() + // TODO Handle navigation to the Event + data class Inputs(val eventId: EventId?) : NodeInputs + interface Callback : Plugin { fun onRoomDetailsClicked() fun onEventClicked(event: TimelineItem.Event): Boolean fun onPreviewAttachments(attachments: ImmutableList) fun onUserDataClicked(userId: UserId) + fun onPermalinkClicked(data: PermalinkData) fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) fun onForwardEventClicked(eventId: EventId) fun onReportMessage(eventId: EventId, senderId: UserId) @@ -109,16 +115,19 @@ class MessagesNode @AssistedInject constructor( ) { when (val permalink = permalinkParser.parse(url)) { is PermalinkData.UserLink -> { - callback?.onUserDataClicked(permalink.userId) - } - is PermalinkData.RoomLink -> { - // TODO Implement room link handling - } - is PermalinkData.EventIdAliasLink -> { - // TODO Implement room and Event link handling + if (permalink.userId in room.membersStateFlow.value.roomMembers().orEmpty().map { it.userId }) { + // Open the room member profile + callback?.onUserDataClicked(permalink.userId) + } else { + // The user is not a member of the room + callback?.onPermalinkClicked(permalink) + } } + is PermalinkData.RoomIdLink, + is PermalinkData.RoomAliasLink, + is PermalinkData.EventIdAliasLink, is PermalinkData.EventIdLink -> { - // TODO Implement room and Event link handling + callback?.onPermalinkClicked(permalink) } is PermalinkData.FallbackLink, is PermalinkData.RoomEmailInviteLink -> {