Add Forward action to MediaDetailsBottomSheet. Closes #5454
Improve API of Callback when forwarding Event.
This commit is contained in:
committed by
Benoit Marty
parent
e9cfce915a
commit
21bae4aee2
@@ -342,10 +342,6 @@ class LoggedInFlowNode(
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames))
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
sessionCoroutineScope.launch { attachRoom(roomId.toRoomIdOrAlias(), clearBackstack = false) }
|
||||
}
|
||||
|
||||
override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) {
|
||||
when (data) {
|
||||
is PermalinkData.UserLink -> {
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
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
|
||||
@@ -24,6 +25,7 @@ import io.element.android.appnav.di.RoomGraphFactory
|
||||
import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode.Inputs
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode.NavTarget
|
||||
import io.element.android.features.forward.api.ForwardEntryPoint
|
||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||
import io.element.android.features.space.api.SpaceEntryPoint
|
||||
@@ -43,6 +45,8 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
@@ -55,6 +59,7 @@ class JoinedRoomLoadedFlowNode(
|
||||
private val messagesEntryPoint: MessagesEntryPoint,
|
||||
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
|
||||
private val spaceEntryPoint: SpaceEntryPoint,
|
||||
private val forwardEntryPoint: ForwardEntryPoint,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
@@ -72,7 +77,6 @@ class JoinedRoomLoadedFlowNode(
|
||||
interface Callback : Plugin {
|
||||
fun onOpenRoom(roomId: RoomId, serverNames: List<String>)
|
||||
fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean)
|
||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||
fun onOpenGlobalNotificationSettings()
|
||||
}
|
||||
|
||||
@@ -130,8 +134,8 @@ class JoinedRoomLoadedFlowNode(
|
||||
callbacks.forEach { it.onPermalinkClick(data, pushToBackstack) }
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
callbacks.forEach { it.onForwardedToSingleRoom(roomId) }
|
||||
override fun forwardEvent(eventId: EventId) {
|
||||
backstack.push(NavTarget.ForwardEvent(eventId))
|
||||
}
|
||||
}
|
||||
return roomDetailsEntryPoint.nodeBuilder(this, buildContext)
|
||||
@@ -157,6 +161,22 @@ class JoinedRoomLoadedFlowNode(
|
||||
NavTarget.Space -> {
|
||||
createSpaceNode(buildContext)
|
||||
}
|
||||
is NavTarget.ForwardEvent -> {
|
||||
val timelineProvider = { MutableStateFlow(inputs.room.liveTimeline).asStateFlow() }
|
||||
val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider)
|
||||
val callback = object : ForwardEntryPoint.Callback {
|
||||
override fun onForwardDone(roomIds: List<RoomId>) {
|
||||
backstack.pop()
|
||||
roomIds.singleOrNull()?.let { roomId ->
|
||||
callbacks.forEach { it.onOpenRoom(roomId, emptyList()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
forwardEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(params)
|
||||
.callback(callback)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +213,12 @@ class JoinedRoomLoadedFlowNode(
|
||||
callbacks.forEach { it.onPermalinkClick(data, pushToBackstack) }
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
callbacks.forEach { it.onForwardedToSingleRoom(roomId) }
|
||||
override fun forwardEvent(eventId: EventId) {
|
||||
backstack.push(NavTarget.ForwardEvent(eventId))
|
||||
}
|
||||
|
||||
override fun openRoom(roomId: RoomId) {
|
||||
callbacks.forEach { it.onOpenRoom(roomId, emptyList()) }
|
||||
}
|
||||
}
|
||||
val params = MessagesEntryPoint.Params(
|
||||
@@ -219,6 +243,9 @@ class JoinedRoomLoadedFlowNode(
|
||||
@Parcelize
|
||||
data class RoomMemberDetails(val userId: UserId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class ForwardEvent(val eventId: EventId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object RoomNotificationSettings : NavTarget
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.di.RoomGraphFactory
|
||||
import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
|
||||
import io.element.android.features.forward.api.ForwardEntryPoint
|
||||
import io.element.android.features.messages.api.MessagesEntryPoint
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
|
||||
import io.element.android.features.space.api.SpaceEntryPoint
|
||||
@@ -122,11 +123,22 @@ class JoinedRoomLoadedFlowNodeTest {
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeForwardEntryPoint : ForwardEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ForwardEntryPoint.NodeBuilder {
|
||||
return object : ForwardEntryPoint.NodeBuilder {
|
||||
override fun params(params: ForwardEntryPoint.Params) = this
|
||||
override fun callback(callback: ForwardEntryPoint.Callback) = this
|
||||
override fun build() = node(buildContext) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createJoinedRoomLoadedFlowNode(
|
||||
plugins: List<Plugin>,
|
||||
messagesEntryPoint: MessagesEntryPoint = FakeMessagesEntryPoint(),
|
||||
roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(),
|
||||
spaceEntryPoint: SpaceEntryPoint = FakeSpaceEntryPoint(),
|
||||
forwardEntryPoint: ForwardEntryPoint = FakeForwardEntryPoint(),
|
||||
activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(),
|
||||
) = JoinedRoomLoadedFlowNode(
|
||||
buildContext = BuildContext.root(savedStateMap = null),
|
||||
@@ -134,6 +146,7 @@ class JoinedRoomLoadedFlowNodeTest {
|
||||
messagesEntryPoint = messagesEntryPoint,
|
||||
roomDetailsEntryPoint = roomDetailsEntryPoint,
|
||||
spaceEntryPoint = spaceEntryPoint,
|
||||
forwardEntryPoint = forwardEntryPoint,
|
||||
appNavigationStateService = FakeAppNavigationStateService(),
|
||||
sessionCoroutineScope = this,
|
||||
roomGraphFactory = FakeRoomGraphFactory(),
|
||||
|
||||
@@ -24,7 +24,7 @@ interface ForwardEntryPoint : FeatureEntryPoint {
|
||||
}
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onForwardedToSingleRoom(roomId: RoomId)
|
||||
fun onForwardDone(roomIds: List<RoomId>)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.first
|
||||
* It could be the live timeline, a pinned timeline or a detached timeline.
|
||||
* By default, the active timeline is the live timeline.
|
||||
*/
|
||||
interface TimelineProvider {
|
||||
fun interface TimelineProvider {
|
||||
fun activeTimelineFlow(): StateFlow<Timeline?>
|
||||
}
|
||||
|
||||
|
||||
@@ -24,5 +24,6 @@ interface MediaGalleryEntryPoint : FeatureEntryPoint {
|
||||
interface Callback : Plugin {
|
||||
fun onBackClick()
|
||||
fun onViewInTimeline(eventId: EventId)
|
||||
fun forwardEvent(eventId: EventId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
|
||||
interface Callback : Plugin {
|
||||
fun onDone()
|
||||
fun onViewInTimeline(eventId: EventId)
|
||||
fun onForwardEvent(eventId: EventId)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
|
||||
@@ -33,6 +33,7 @@ dependencies {
|
||||
implementation(libs.telephoto.flick)
|
||||
|
||||
implementation(projects.features.enterprise.api)
|
||||
implementation(projects.features.forward.api)
|
||||
implementation(projects.features.viewfolder.api)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
|
||||
@@ -49,6 +49,7 @@ fun MediaDetailsBottomSheet(
|
||||
state: MediaBottomSheetState.MediaDetailsBottomSheetState,
|
||||
onViewInTimeline: (EventId) -> Unit,
|
||||
onShare: (EventId) -> Unit,
|
||||
onForward: (EventId) -> Unit,
|
||||
onDownload: (EventId) -> Unit,
|
||||
onDelete: (EventId) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
@@ -102,6 +103,14 @@ fun MediaDetailsBottomSheet(
|
||||
onShare(state.eventId)
|
||||
}
|
||||
)
|
||||
ListItem(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Forward())),
|
||||
headlineContent = { Text(stringResource(CommonStrings.action_forward)) },
|
||||
style = ListItemStyle.Primary,
|
||||
onClick = {
|
||||
onForward(state.eventId)
|
||||
}
|
||||
)
|
||||
ListItem(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Download())),
|
||||
headlineContent = { Text(stringResource(CommonStrings.action_save)) },
|
||||
@@ -216,6 +225,7 @@ internal fun MediaDetailsBottomSheetPreview() = ElementPreview {
|
||||
state = aMediaDetailsBottomSheetState(),
|
||||
onViewInTimeline = {},
|
||||
onShare = {},
|
||||
onForward = {},
|
||||
onDownload = {},
|
||||
onDelete = {},
|
||||
onDismiss = {},
|
||||
|
||||
@@ -17,6 +17,7 @@ sealed interface MediaGalleryEvents {
|
||||
data class ChangeMode(val mode: MediaGalleryMode) : MediaGalleryEvents
|
||||
data class LoadMore(val direction: Timeline.PaginationDirection) : MediaGalleryEvents
|
||||
data class Share(val eventId: EventId?) : MediaGalleryEvents
|
||||
data class Forward(val eventId: EventId) : MediaGalleryEvents
|
||||
data class SaveOnDisk(val eventId: EventId?) : MediaGalleryEvents
|
||||
data class OpenInfo(val mediaItem: MediaItem.Event) : MediaGalleryEvents
|
||||
data class ViewInTimeline(val eventId: EventId) : MediaGalleryEvents
|
||||
|
||||
@@ -11,4 +11,5 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
interface MediaGalleryNavigator {
|
||||
fun onViewInTimelineClick(eventId: EventId)
|
||||
fun onForwardClick(eventId: EventId)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class MediaGalleryNode(
|
||||
fun onBackClick()
|
||||
fun onItemClick(item: MediaItem.Event)
|
||||
fun onViewInTimeline(eventId: EventId)
|
||||
fun onForward(eventId: EventId)
|
||||
}
|
||||
|
||||
private fun onBackClick() {
|
||||
@@ -54,6 +55,12 @@ class MediaGalleryNode(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onForwardClick(eventId: EventId) {
|
||||
plugins<Callback>().forEach {
|
||||
it.onForward(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onItemClick(item: MediaItem.Event) {
|
||||
plugins<Callback>().forEach {
|
||||
it.onItemClick(item)
|
||||
|
||||
@@ -105,6 +105,10 @@ class MediaGalleryPresenter(
|
||||
share(it)
|
||||
}
|
||||
}
|
||||
is MediaGalleryEvents.Forward -> {
|
||||
mediaBottomSheetState = MediaBottomSheetState.Hidden
|
||||
navigator.onForwardClick(event.eventId)
|
||||
}
|
||||
is MediaGalleryEvents.ViewInTimeline -> {
|
||||
mediaBottomSheetState = MediaBottomSheetState.Hidden
|
||||
navigator.onViewInTimelineClick(event.eventId)
|
||||
|
||||
@@ -166,6 +166,9 @@ fun MediaGalleryView(
|
||||
onShare = { eventId ->
|
||||
state.eventSink(MediaGalleryEvents.Share(eventId))
|
||||
},
|
||||
onForward = { eventId ->
|
||||
state.eventSink(MediaGalleryEvents.Forward(eventId))
|
||||
},
|
||||
onDownload = { eventId ->
|
||||
state.eventSink(MediaGalleryEvents.SaveOnDisk(eventId))
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@ import kotlinx.parcelize.Parcelize
|
||||
class MediaGalleryFlowNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val mediaViewerEntryPoint: MediaViewerEntryPoint
|
||||
private val mediaViewerEntryPoint: MediaViewerEntryPoint,
|
||||
) : BaseFlowNode<MediaGalleryFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
@@ -82,6 +82,12 @@ class MediaGalleryFlowNode(
|
||||
}
|
||||
}
|
||||
|
||||
private fun forwardEvent(eventId: EventId) {
|
||||
plugins<MediaGalleryEntryPoint.Callback>().forEach {
|
||||
it.forwardEvent(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Root -> {
|
||||
@@ -94,6 +100,10 @@ class MediaGalleryFlowNode(
|
||||
this@MediaGalleryFlowNode.onViewInTimeline(eventId)
|
||||
}
|
||||
|
||||
override fun onForward(eventId: EventId) {
|
||||
forwardEvent(eventId)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MediaItem.Event) {
|
||||
val mode = when (item) {
|
||||
is MediaItem.Audio,
|
||||
@@ -124,6 +134,11 @@ class MediaGalleryFlowNode(
|
||||
override fun onViewInTimeline(eventId: EventId) {
|
||||
this@MediaGalleryFlowNode.onViewInTimeline(eventId)
|
||||
}
|
||||
|
||||
override fun onForwardEvent(eventId: EventId) {
|
||||
// Need to go to the parent because of the overlay
|
||||
forwardEvent(eventId)
|
||||
}
|
||||
}
|
||||
mediaViewerEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(
|
||||
|
||||
@@ -17,6 +17,7 @@ sealed interface MediaViewerEvents {
|
||||
data class OpenWith(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents
|
||||
data class ClearLoadingError(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents
|
||||
data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents
|
||||
data class Forward(val eventId: EventId) : MediaViewerEvents
|
||||
data class OpenInfo(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents
|
||||
data class ConfirmDelete(
|
||||
val eventId: EventId,
|
||||
|
||||
@@ -11,5 +11,6 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
|
||||
interface MediaViewerNavigator {
|
||||
fun onViewInTimelineClick(eventId: EventId)
|
||||
fun onForwardClick(eventId: EventId)
|
||||
fun onItemDeleted()
|
||||
}
|
||||
|
||||
@@ -71,6 +71,12 @@ class MediaViewerNode(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onForwardClick(eventId: EventId) {
|
||||
plugins<MediaViewerEntryPoint.Callback>().forEach {
|
||||
it.onForwardEvent(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemDeleted() {
|
||||
onDone()
|
||||
}
|
||||
|
||||
@@ -117,6 +117,10 @@ class MediaViewerPresenter(
|
||||
mediaBottomSheetState = MediaBottomSheetState.Hidden
|
||||
navigator.onViewInTimelineClick(event.eventId)
|
||||
}
|
||||
is MediaViewerEvents.Forward -> {
|
||||
mediaBottomSheetState = MediaBottomSheetState.Hidden
|
||||
navigator.onForwardClick(event.eventId)
|
||||
}
|
||||
is MediaViewerEvents.OpenInfo -> coroutineScope.launch {
|
||||
mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState(
|
||||
eventId = event.data.eventId,
|
||||
|
||||
@@ -247,6 +247,9 @@ fun MediaViewerView(
|
||||
state.eventSink(MediaViewerEvents.Share(currentData))
|
||||
}
|
||||
},
|
||||
onForward = {
|
||||
state.eventSink(MediaViewerEvents.Forward(it))
|
||||
},
|
||||
onDownload = {
|
||||
(currentData as? MediaViewerPageData.MediaViewerData)?.let {
|
||||
state.eventSink(MediaViewerEvents.SaveOnDisk(currentData))
|
||||
|
||||
@@ -37,12 +37,13 @@ class DefaultMediaGalleryEntryPointTest {
|
||||
plugins = plugins,
|
||||
mediaViewerEntryPoint = object : MediaViewerEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
val callback = object : MediaGalleryEntryPoint.Callback {
|
||||
override fun onBackClick() = lambdaError()
|
||||
override fun onViewInTimeline(eventId: EventId) = lambdaError()
|
||||
override fun forwardEvent(eventId: EventId) = lambdaError()
|
||||
}
|
||||
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
|
||||
.callback(callback)
|
||||
|
||||
@@ -72,6 +72,7 @@ class DefaultMediaViewerEntryPointTest {
|
||||
val callback = object : MediaViewerEntryPoint.Callback {
|
||||
override fun onDone() = lambdaError()
|
||||
override fun onViewInTimeline(eventId: EventId) = lambdaError()
|
||||
override fun onForwardEvent(eventId: EventId) = lambdaError()
|
||||
}
|
||||
val params = createMediaViewerEntryPointParams()
|
||||
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
|
||||
@@ -115,6 +116,7 @@ class DefaultMediaViewerEntryPointTest {
|
||||
val callback = object : MediaViewerEntryPoint.Callback {
|
||||
override fun onDone() = lambdaError()
|
||||
override fun onViewInTimeline(eventId: EventId) = lambdaError()
|
||||
override fun onForwardEvent(eventId: EventId) = lambdaError()
|
||||
}
|
||||
val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null))
|
||||
.avatar(
|
||||
|
||||
@@ -56,6 +56,19 @@ class MediaDetailsBottomSheetTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on Forward invokes expected callback`() {
|
||||
val state = aMediaDetailsBottomSheetState()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onForward = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_forward)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on Save invokes expected callback`() {
|
||||
@@ -100,6 +113,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMedia
|
||||
state: MediaBottomSheetState.MediaDetailsBottomSheetState,
|
||||
onViewInTimeline: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onShare: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onForward: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onDownload: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onDismiss: () -> Unit = EnsureNeverCalled(),
|
||||
@@ -109,6 +123,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMedia
|
||||
state = state,
|
||||
onViewInTimeline = onViewInTimeline,
|
||||
onShare = onShare,
|
||||
onForward = onForward,
|
||||
onDownload = onDownload,
|
||||
onDelete = onDelete,
|
||||
onDismiss = onDismiss,
|
||||
|
||||
@@ -11,9 +11,14 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeMediaGalleryNavigator(
|
||||
private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() }
|
||||
private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() },
|
||||
private val onForwardClickLambda: (EventId) -> Unit = { lambdaError() },
|
||||
) : MediaGalleryNavigator {
|
||||
override fun onViewInTimelineClick(eventId: EventId) {
|
||||
onViewInTimelineClickLambda(eventId)
|
||||
}
|
||||
|
||||
override fun onForwardClick(eventId: EventId) {
|
||||
onForwardClickLambda(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,17 @@ import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeMediaViewerNavigator(
|
||||
private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() },
|
||||
private val onForwardClickLambda: (EventId) -> Unit = { lambdaError() },
|
||||
private val onItemDeletedLambda: () -> Unit = { lambdaError() },
|
||||
) : MediaViewerNavigator {
|
||||
override fun onViewInTimelineClick(eventId: EventId) {
|
||||
onViewInTimelineClickLambda(eventId)
|
||||
}
|
||||
|
||||
override fun onForwardClick(eventId: EventId) {
|
||||
onForwardClickLambda(eventId)
|
||||
}
|
||||
|
||||
override fun onItemDeleted() {
|
||||
onItemDeletedLambda()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user