Handle permalink navigation - WIP
- prepare navigating to an Event - add NodeBuilder to MessagesEntryPoint
This commit is contained in:
parent
fb3d49cc2f
commit
6a46555462
11 changed files with 186 additions and 43 deletions
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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 -> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue