Add Forward action to MediaDetailsBottomSheet. Closes #5454
Improve API of Callback when forwarding Event.
This commit is contained in:
parent
e9cfce915a
commit
21bae4aee2
35 changed files with 190 additions and 36 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue