Handle permalink navigation - WIP

- prepare navigating to an Event
- add NodeBuilder to MessagesEntryPoint
This commit is contained in:
Benoit Marty 2024-04-16 10:21:26 +02:00 committed by Benoit Marty
parent fb3d49cc2f
commit 6a46555462
11 changed files with 186 additions and 43 deletions

View file

@ -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.MatrixClient
import io.element.android.libraries.matrix.api.core.MAIN_SPACE 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.core.RoomId
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -191,7 +192,7 @@ class LoggedInFlowNode @AssistedInject constructor(
data class Room( data class Room(
val roomId: RoomId, val roomId: RoomId,
val roomDescription: RoomDescription? = null, val roomDescription: RoomDescription? = null,
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(null)
) : NavTarget ) : NavTarget
@Parcelize @Parcelize
@ -270,6 +271,40 @@ class LoggedInFlowNode @AssistedInject constructor(
coroutineScope.launch { attachRoom(roomId) } 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() { override fun onOpenGlobalNotificationSettings() {
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings)) backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
} }

View file

@ -75,7 +75,7 @@ class RoomFlowNode @AssistedInject constructor(
data class Inputs( data class Inputs(
val roomId: RoomId, val roomId: RoomId,
val roomDescription: Optional<RoomDescription>, val roomDescription: Optional<RoomDescription>,
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
) : NodeInputs ) : NodeInputs
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()

View file

@ -16,8 +16,17 @@
package io.element.android.appnav.room package io.element.android.appnav.room
enum class RoomNavigationTarget { import android.os.Parcelable
Messages, import io.element.android.libraries.matrix.api.core.EventId
Details, import kotlinx.parcelize.Parcelize
NotificationSettings,
sealed interface RoomNavigationTarget : Parcelable {
@Parcelize
data class Messages(val eventId: EventId? = null) : RoomNavigationTarget
@Parcelize
data object Details : RoomNavigationTarget
@Parcelize
data object NotificationSettings : RoomNavigationTarget
} }

View file

@ -69,7 +69,7 @@ class JoinedRoomFlowNode @AssistedInject constructor(
) { ) {
data class Inputs( data class Inputs(
val roomId: RoomId, val roomId: RoomId,
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
) : NodeInputs ) : NodeInputs
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()

View file

@ -42,8 +42,10 @@ import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.DaggerComponentOwner
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
import io.element.android.libraries.matrix.api.core.UserId 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.libraries.matrix.api.room.MatrixRoom
import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -63,8 +65,8 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
roomComponentFactory: RoomComponentFactory, roomComponentFactory: RoomComponentFactory,
) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>( ) : BaseFlowNode<JoinedRoomLoadedFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = when (plugins.filterIsInstance(Inputs::class.java).first().initialElement) { initialElement = when (val input = plugins.filterIsInstance(Inputs::class.java).first().initialElement) {
RoomNavigationTarget.Messages -> NavTarget.Messages is RoomNavigationTarget.Messages -> NavTarget.Messages(input.eventId)
RoomNavigationTarget.Details -> NavTarget.RoomDetails RoomNavigationTarget.Details -> NavTarget.RoomDetails
RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings
}, },
@ -75,13 +77,14 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
), DaggerComponentOwner { ), DaggerComponentOwner {
interface Callback : Plugin { interface Callback : Plugin {
fun onOpenRoom(roomId: RoomId) fun onOpenRoom(roomId: RoomId)
fun onPermalinkClicked(data: PermalinkData)
fun onForwardedToSingleRoom(roomId: RoomId) fun onForwardedToSingleRoom(roomId: RoomId)
fun onOpenGlobalNotificationSettings() fun onOpenGlobalNotificationSettings()
} }
data class Inputs( data class Inputs(
val room: MatrixRoom, val room: MatrixRoom,
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages, val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(),
) : NodeInputs ) : NodeInputs
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
@ -139,7 +142,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
NavTarget.Messages -> { is NavTarget.Messages -> {
val callback = object : MessagesEntryPoint.Callback { val callback = object : MessagesEntryPoint.Callback {
override fun onRoomDetailsClicked() { override fun onRoomDetailsClicked() {
backstack.push(NavTarget.RoomDetails) backstack.push(NavTarget.RoomDetails)
@ -149,11 +152,18 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
backstack.push(NavTarget.RoomMemberDetails(userId)) backstack.push(NavTarget.RoomMemberDetails(userId))
} }
override fun onPermalinkClicked(data: PermalinkData) {
callbacks.forEach { it.onPermalinkClicked(data) }
}
override fun onForwardedToSingleRoom(roomId: RoomId) { override fun onForwardedToSingleRoom(roomId: RoomId) {
callbacks.forEach { it.onForwardedToSingleRoom(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 -> { NavTarget.RoomDetails -> {
createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomDetails) createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomDetails)
@ -169,7 +179,7 @@ class JoinedRoomLoadedFlowNode @AssistedInject constructor(
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
data object Messages : NavTarget data class Messages(val eventId: EventId? = null) : NavTarget
@Parcelize @Parcelize
data object RoomDetails : NavTarget data object RoomDetails : NavTarget

View file

@ -47,14 +47,30 @@ class JoinRoomLoadedFlowNodeTest {
@get:Rule @get:Rule
val mainDispatcherRule = MainDispatcherRule() val mainDispatcherRule = MainDispatcherRule()
private class FakeMessagesEntryPoint : MessagesEntryPoint { private class FakeMessagesEntryPoint : MessagesEntryPoint, MessagesEntryPoint.NodeBuilder {
var buildContext: BuildContext? = null
var nodeId: String? = null var nodeId: String? = null
var parameters: MessagesEntryPoint.Params? = null
var callback: MessagesEntryPoint.Callback? = null var callback: MessagesEntryPoint.Callback? = null
override fun createNode(parentNode: Node, buildContext: BuildContext, callback: MessagesEntryPoint.Callback): Node { override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder {
return node(buildContext) {}.also { 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 nodeId = it.id
this.callback = callback
} }
} }
} }
@ -118,9 +134,9 @@ class JoinRoomLoadedFlowNodeTest {
val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper() val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper()
// THEN // THEN
assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages) assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Messages())
roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages, Lifecycle.State.CREATED) roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Messages(), Lifecycle.State.CREATED)
val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages)!! val messagesNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Messages())!!
assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId) assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId)
} }

View file

@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents 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.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership 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.MatrixRoomInfo
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
import kotlinx.coroutines.launch
import java.util.Optional import java.util.Optional
class JoinRoomPresenter @AssistedInject constructor( class JoinRoomPresenter @AssistedInject constructor(
@ -46,6 +49,7 @@ class JoinRoomPresenter @AssistedInject constructor(
@Composable @Composable
override fun present(): JoinRoomState { override fun present(): JoinRoomState {
val coroutineScope = rememberCoroutineScope()
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty()) val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
val contentState by produceState<ContentState>(initialValue = ContentState.Loading(roomId), key1 = roomInfo) { val contentState by produceState<ContentState>(initialValue = ContentState.Loading(roomId), key1 = roomInfo) {
value = when { value = when {
@ -56,6 +60,12 @@ class JoinRoomPresenter @AssistedInject constructor(
roomDescription.get().toContentState() roomDescription.get().toContentState()
} }
else -> { else -> {
coroutineScope.launch {
val result = matrixClient.getRoomPreview(roomId.value)
value = result.getOrNull()
?.toContentState()
?: ContentState.UnknownRoom(roomId)
}
ContentState.Loading(roomId) ContentState.Loading(roomId)
} }
} }
@ -64,7 +74,8 @@ class JoinRoomPresenter @AssistedInject constructor(
fun handleEvents(event: JoinRoomEvents) { fun handleEvents(event: JoinRoomEvents) {
when (event) { when (event) {
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> { JoinRoomEvents.AcceptInvite,
JoinRoomEvents.JoinRoom -> {
val inviteData = contentState.toInviteData() ?: return val inviteData = contentState.toInviteData() ?: return
acceptDeclineInviteState.eventSink( acceptDeclineInviteState.eventSink(
AcceptDeclineInviteEvents.AcceptInvite(inviteData) 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 @VisibleForTesting
internal fun RoomDescription.toContentState(): ContentState { internal fun RoomDescription.toContentState(): ContentState {
return ContentState.Loaded( return ContentState.Loaded(

View file

@ -20,19 +20,28 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint 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.RoomId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkData
interface MessagesEntryPoint : FeatureEntryPoint { interface MessagesEntryPoint : FeatureEntryPoint {
fun createNode( fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
parentNode: Node,
buildContext: BuildContext, interface NodeBuilder {
callback: Callback, fun params(params: Params): NodeBuilder
): Node fun callback(callback: Callback): NodeBuilder
fun build(): Node
}
data class Params(
val eventId: EventId?,
)
interface Callback : Plugin { interface Callback : Plugin {
fun onRoomDetailsClicked() fun onRoomDetailsClicked()
fun onUserDataClicked(userId: UserId) fun onUserDataClicked(userId: UserId)
fun onPermalinkClicked(data: PermalinkData)
fun onForwardedToSingleRoom(roomId: RoomId) fun onForwardedToSingleRoom(roomId: RoomId)
} }
} }

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
@ -26,11 +27,23 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint { class DefaultMessagesEntryPoint @Inject constructor() : MessagesEntryPoint {
override fun createNode( override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder {
parentNode: Node, val plugins = ArrayList<Plugin>()
buildContext: BuildContext,
callback: MessagesEntryPoint.Callback return object : MessagesEntryPoint.NodeBuilder {
): Node { override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder {
return parentNode.createNode<MessagesFlowNode>(buildContext, listOf(callback)) 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<MessagesFlowNode>(buildContext, plugins)
}
}
} }
} }

View file

@ -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.features.poll.api.create.CreatePollMode
import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BackstackWithOverlayBox
import io.element.android.libraries.architecture.BaseFlowNode 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.createNode
import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.Overlay
import io.element.android.libraries.architecture.overlay.operation.show 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.RoomId
import io.element.android.libraries.matrix.api.core.UserId 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.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.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.mediaviewer.api.local.MediaInfo import io.element.android.libraries.mediaviewer.api.local.MediaInfo
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
@ -79,7 +81,7 @@ class MessagesFlowNode @AssistedInject constructor(
private val createPollEntryPoint: CreatePollEntryPoint, private val createPollEntryPoint: CreatePollEntryPoint,
) : BaseFlowNode<MessagesFlowNode.NavTarget>( ) : BaseFlowNode<MessagesFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.Messages, initialElement = NavTarget.Messages(plugins.filterIsInstance<Inputs>().firstOrNull()?.eventId),
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
overlay = Overlay( overlay = Overlay(
@ -88,12 +90,16 @@ class MessagesFlowNode @AssistedInject constructor(
buildContext = buildContext, buildContext = buildContext,
plugins = plugins plugins = plugins
) { ) {
data class Inputs(val eventId: EventId?) : NodeInputs
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
data object Empty : NavTarget data object Empty : NavTarget
@Parcelize @Parcelize
data object Messages : NavTarget data class Messages(
val eventId: EventId? = null,
) : NavTarget
@Parcelize @Parcelize
data class MediaViewer( data class MediaViewer(
@ -149,6 +155,10 @@ class MessagesFlowNode @AssistedInject constructor(
callback?.onUserDataClicked(userId) callback?.onUserDataClicked(userId)
} }
override fun onPermalinkClicked(data: PermalinkData) {
callback?.onPermalinkClicked(data)
}
override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) {
backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo))
} }
@ -181,7 +191,10 @@ class MessagesFlowNode @AssistedInject constructor(
ElementCallActivity.start(context, inputs) ElementCallActivity.start(context, inputs)
} }
} }
createNode<MessagesNode>(buildContext, listOf(callback)) val params = MessagesNode.Inputs(
eventId = navTarget.eventId,
)
createNode<MessagesNode>(buildContext, listOf(callback, params))
} }
is NavTarget.MediaViewer -> { is NavTarget.MediaViewer -> {
val inputs = MediaViewerNode.Inputs( val inputs = MediaViewerNode.Inputs(

View file

@ -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.di.TimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.androidutils.system.openUrlInExternalApp 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.core.bool.orFalse
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.EventId 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.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser 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.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.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.mediaplayer.api.MediaPlayer
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ -62,11 +64,15 @@ class MessagesNode @AssistedInject constructor(
private val presenter = presenterFactory.create(this) private val presenter = presenterFactory.create(this)
private val callback = plugins<Callback>().firstOrNull() private val callback = plugins<Callback>().firstOrNull()
// TODO Handle navigation to the Event
data class Inputs(val eventId: EventId?) : NodeInputs
interface Callback : Plugin { interface Callback : Plugin {
fun onRoomDetailsClicked() fun onRoomDetailsClicked()
fun onEventClicked(event: TimelineItem.Event): Boolean fun onEventClicked(event: TimelineItem.Event): Boolean
fun onPreviewAttachments(attachments: ImmutableList<Attachment>) fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
fun onUserDataClicked(userId: UserId) fun onUserDataClicked(userId: UserId)
fun onPermalinkClicked(data: PermalinkData)
fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo)
fun onForwardEventClicked(eventId: EventId) fun onForwardEventClicked(eventId: EventId)
fun onReportMessage(eventId: EventId, senderId: UserId) fun onReportMessage(eventId: EventId, senderId: UserId)
@ -109,16 +115,19 @@ class MessagesNode @AssistedInject constructor(
) { ) {
when (val permalink = permalinkParser.parse(url)) { when (val permalink = permalinkParser.parse(url)) {
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
callback?.onUserDataClicked(permalink.userId) if (permalink.userId in room.membersStateFlow.value.roomMembers().orEmpty().map { it.userId }) {
} // Open the room member profile
is PermalinkData.RoomLink -> { callback?.onUserDataClicked(permalink.userId)
// TODO Implement room link handling } else {
} // The user is not a member of the room
is PermalinkData.EventIdAliasLink -> { callback?.onPermalinkClicked(permalink)
// TODO Implement room and Event link handling }
} }
is PermalinkData.RoomIdLink,
is PermalinkData.RoomAliasLink,
is PermalinkData.EventIdAliasLink,
is PermalinkData.EventIdLink -> { is PermalinkData.EventIdLink -> {
// TODO Implement room and Event link handling callback?.onPermalinkClicked(permalink)
} }
is PermalinkData.FallbackLink, is PermalinkData.FallbackLink,
is PermalinkData.RoomEmailInviteLink -> { is PermalinkData.RoomEmailInviteLink -> {