Add Forward action to MediaDetailsBottomSheet. Closes #5454

Improve API of Callback when forwarding Event.
This commit is contained in:
Benoit Marty 2025-10-28 16:02:37 +01:00 committed by Benoit Marty
parent e9cfce915a
commit 21bae4aee2
35 changed files with 190 additions and 36 deletions

View file

@ -24,7 +24,7 @@ interface ForwardEntryPoint : FeatureEntryPoint {
}
interface Callback : Plugin {
fun onForwardedToSingleRoom(roomId: RoomId)
fun onForwardDone(roomIds: List<RoomId>)
}
data class Params(

View file

@ -13,9 +13,9 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SessionScope
@ContributesBinding(RoomScope::class)
@ContributesBinding(SessionScope::class)
class DefaultForwardEntryPoint : ForwardEntryPoint {
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ForwardEntryPoint.NodeBuilder {
val plugins = ArrayList<Plugin>()

View file

@ -23,7 +23,7 @@ import io.element.android.annotations.ContributesNode
import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SessionScope
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.timeline.TimelineProvider
@ -31,7 +31,7 @@ import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint
import io.element.android.libraries.roomselect.api.RoomSelectMode
import kotlinx.parcelize.Parcelize
@ContributesNode(RoomScope::class)
@ContributesNode(SessionScope::class)
@AssistedInject
class ForwardMessagesNode(
@Assisted buildContext: BuildContext,
@ -65,7 +65,7 @@ class ForwardMessagesNode(
}
override fun onCancel() {
navigateUp()
onForwardDone(emptyList())
}
}
@ -86,16 +86,12 @@ class ForwardMessagesNode(
val state = presenter.present()
ForwardMessagesView(
state = state,
onForwardSuccess = ::onForwardSuccess,
onForwardSuccess = ::onForwardDone,
)
}
}
private fun onForwardSuccess(roomIds: List<RoomId>) {
navigateUp()
if (roomIds.size == 1) {
val targetRoomId = roomIds.first()
callbacks.forEach { it.onForwardedToSingleRoom(targetRoomId) }
}
private fun onForwardDone(roomIds: List<RoomId>) {
callbacks.forEach { it.onForwardDone(roomIds) }
}
}

View file

@ -46,7 +46,7 @@ class DefaultForwardEntryPointTest {
)
}
val callback = object : ForwardEntryPoint.Callback {
override fun onForwardedToSingleRoom(roomId: RoomId) = lambdaError()
override fun onForwardDone(roomIds: List<RoomId>) = lambdaError()
}
val params = ForwardEntryPoint.Params(
eventId = AN_EVENT_ID,

View file

@ -38,7 +38,8 @@ interface MessagesEntryPoint : FeatureEntryPoint {
fun onRoomDetailsClick()
fun onUserDataClick(userId: UserId)
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
fun onForwardedToSingleRoom(roomId: RoomId)
fun forwardEvent(eventId: EventId)
fun openRoom(roomId: RoomId)
}
data class Params(val initialTarget: InitialTarget) : NodeInputs

View file

@ -18,6 +18,7 @@ import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.pop
import com.bumble.appyx.navmodel.backstack.operation.push
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
@ -150,7 +151,10 @@ class MessagesFlowNode(
data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget
@Parcelize
data class ForwardEvent(val eventId: EventId, val fromPinnedEvents: Boolean) : NavTarget
data class ForwardEvent(
val eventId: EventId,
val fromPinnedEvents: Boolean,
) : NavTarget
@Parcelize
data class ReportMessage(val eventId: EventId, val senderId: UserId) : NavTarget
@ -306,6 +310,11 @@ class MessagesFlowNode(
override fun onViewInTimeline(eventId: EventId) {
viewInTimeline(eventId)
}
override fun onForwardEvent(eventId: EventId) {
// Need to go to the parent because of the overlay
forwardEvent(eventId)
}
}
mediaViewerEntryPoint.nodeBuilder(this, buildContext)
.params(params)
@ -336,8 +345,11 @@ class MessagesFlowNode(
}
val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider)
val callback = object : ForwardEntryPoint.Callback {
override fun onForwardedToSingleRoom(roomId: RoomId) {
callbacks.forEach { it.onForwardedToSingleRoom(roomId) }
override fun onForwardDone(roomIds: List<RoomId>) {
backstack.pop()
roomIds.singleOrNull()?.let { roomId ->
callbacks.forEach { it.openRoom(roomId) }
}
}
}
forwardEntryPoint.nodeBuilder(this, buildContext)
@ -489,6 +501,10 @@ class MessagesFlowNode(
callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) }
}
private fun forwardEvent(eventId: EventId) {
callbacks.forEach { it.forwardEvent(eventId) }
}
private fun processEventClick(
timelineMode: Timeline.Mode,
event: TimelineItem.Event,

View file

@ -119,7 +119,8 @@ class DefaultMessagesEntryPointTest {
override fun onRoomDetailsClick() = lambdaError()
override fun onUserDataClick(userId: UserId) = lambdaError()
override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
override fun onForwardedToSingleRoom(roomId: RoomId) = lambdaError()
override fun forwardEvent(eventId: EventId) = lambdaError()
override fun openRoom(roomId: RoomId) = lambdaError()
}
val initialTarget = MessagesEntryPoint.InitialTarget.Messages(focusedEventId = AN_EVENT_ID)
val params = MessagesEntryPoint.Params(initialTarget)

View file

@ -13,6 +13,7 @@ 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.architecture.NodeInputs
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
@ -36,7 +37,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
fun onOpenGlobalNotificationSettings()
fun onOpenRoom(roomId: RoomId, serverNames: List<String>)
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
fun onForwardedToSingleRoom(roomId: RoomId)
fun forwardEvent(eventId: EventId)
}
interface NodeBuilder {

View file

@ -294,6 +294,10 @@ class RoomDetailsFlowNode(
override fun onViewInTimeline(eventId: EventId) {
// Cannot happen
}
override fun onForwardEvent(eventId: EventId) {
// Cannot happen
}
}
mediaViewerEntryPoint.nodeBuilder(this, buildContext)
.avatar(
@ -321,6 +325,10 @@ class RoomDetailsFlowNode(
it.onPermalinkClick(permalinkData, pushToBackstack = false)
}
}
override fun forwardEvent(eventId: EventId) {
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.forwardEvent(eventId) }
}
}
mediaGalleryEntryPoint.nodeBuilder(this, buildContext)
.callback(callback)
@ -343,8 +351,12 @@ class RoomDetailsFlowNode(
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onPermalinkClick(data, pushToBackstack) }
}
override fun onForwardedToSingleRoom(roomId: RoomId) {
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onForwardedToSingleRoom(roomId) }
override fun forwardEvent(eventId: EventId) {
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.forwardEvent(eventId) }
}
override fun openRoom(roomId: RoomId) {
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onOpenRoom(roomId, emptyList()) }
}
}
return messagesEntryPoint.nodeBuilder(this, buildContext)

View file

@ -97,7 +97,7 @@ class DefaultRoomDetailsEntryPointTest {
override fun onOpenGlobalNotificationSettings() = lambdaError()
override fun onOpenRoom(roomId: RoomId, serverNames: List<String>) = lambdaError()
override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError()
override fun onForwardedToSingleRoom(roomId: RoomId) = lambdaError()
override fun forwardEvent(eventId: EventId) = lambdaError()
}
val params = RoomDetailsEntryPoint.Params(
initialElement = RoomDetailsEntryPoint.InitialTarget.RoomDetails,

View file

@ -101,6 +101,10 @@ class UserProfileFlowNode(
override fun onViewInTimeline(eventId: EventId) {
// Cannot happen
}
override fun onForwardEvent(eventId: EventId) {
// Cannot happen
}
}
mediaViewerEntryPoint.nodeBuilder(this, buildContext)
.avatar(