Update Matrix Room API and allow media swipe on pinned event only.
This commit is contained in:
parent
37618600d7
commit
c02436d3f0
30 changed files with 268 additions and 181 deletions
|
|
@ -71,6 +71,7 @@ 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.libraries.matrix.api.room.alias.matches
|
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||||
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
|
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
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.matrix.ui.messages.LocalRoomMemberProfilesCache
|
import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache
|
||||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||||
|
|
@ -187,8 +188,11 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
callbacks.forEach { it.onRoomDetailsClick() }
|
callbacks.forEach { it.onRoomDetailsClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEventClick(event: TimelineItem.Event): Boolean {
|
override fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean {
|
||||||
return processEventClick(event)
|
return processEventClick(
|
||||||
|
timelineMode = if (isLive) Timeline.Mode.LIVE else Timeline.Mode.FOCUSED_ON_EVENT,
|
||||||
|
event = event,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>) {
|
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>) {
|
||||||
|
|
@ -316,7 +320,10 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
NavTarget.PinnedMessagesList -> {
|
NavTarget.PinnedMessagesList -> {
|
||||||
val callback = object : PinnedMessagesListNode.Callback {
|
val callback = object : PinnedMessagesListNode.Callback {
|
||||||
override fun onEventClick(event: TimelineItem.Event) {
|
override fun onEventClick(event: TimelineItem.Event) {
|
||||||
processEventClick(event)
|
processEventClick(
|
||||||
|
timelineMode = Timeline.Mode.PINNED_EVENTS,
|
||||||
|
event = event,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDataClick(userId: UserId) {
|
override fun onUserDataClick(userId: UserId) {
|
||||||
|
|
@ -358,11 +365,14 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) }
|
callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processEventClick(event: TimelineItem.Event): Boolean {
|
private fun processEventClick(
|
||||||
|
timelineMode: Timeline.Mode,
|
||||||
|
event: TimelineItem.Event,
|
||||||
|
): Boolean {
|
||||||
val navTarget = when (event.content) {
|
val navTarget = when (event.content) {
|
||||||
is TimelineItemImageContent -> {
|
is TimelineItemImageContent -> {
|
||||||
buildMediaViewerNavTarget(
|
buildMediaViewerNavTarget(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||||
event = event,
|
event = event,
|
||||||
content = event.content,
|
content = event.content,
|
||||||
mediaSource = event.content.mediaSource,
|
mediaSource = event.content.mediaSource,
|
||||||
|
|
@ -374,7 +384,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
if encrypted on certain bridges */
|
if encrypted on certain bridges */
|
||||||
event.content.preferredMediaSource?.let { preferredMediaSource ->
|
event.content.preferredMediaSource?.let { preferredMediaSource ->
|
||||||
buildMediaViewerNavTarget(
|
buildMediaViewerNavTarget(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||||
event = event,
|
event = event,
|
||||||
content = event.content,
|
content = event.content,
|
||||||
mediaSource = preferredMediaSource,
|
mediaSource = preferredMediaSource,
|
||||||
|
|
@ -384,7 +394,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is TimelineItemVideoContent -> {
|
is TimelineItemVideoContent -> {
|
||||||
buildMediaViewerNavTarget(
|
buildMediaViewerNavTarget(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||||
event = event,
|
event = event,
|
||||||
content = event.content,
|
content = event.content,
|
||||||
mediaSource = event.content.mediaSource,
|
mediaSource = event.content.mediaSource,
|
||||||
|
|
@ -393,7 +403,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is TimelineItemFileContent -> {
|
is TimelineItemFileContent -> {
|
||||||
buildMediaViewerNavTarget(
|
buildMediaViewerNavTarget(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
|
||||||
event = event,
|
event = event,
|
||||||
content = event.content,
|
content = event.content,
|
||||||
mediaSource = event.content.mediaSource,
|
mediaSource = event.content.mediaSource,
|
||||||
|
|
@ -402,7 +412,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
is TimelineItemAudioContent -> {
|
is TimelineItemAudioContent -> {
|
||||||
buildMediaViewerNavTarget(
|
buildMediaViewerNavTarget(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
|
||||||
event = event,
|
event = event,
|
||||||
content = event.content,
|
content = event.content,
|
||||||
mediaSource = event.content.mediaSource,
|
mediaSource = event.content.mediaSource,
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class MessagesNode @AssistedInject constructor(
|
||||||
|
|
||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun onRoomDetailsClick()
|
fun onRoomDetailsClick()
|
||||||
fun onEventClick(event: TimelineItem.Event): Boolean
|
fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean
|
||||||
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
||||||
fun onUserDataClick(userId: UserId)
|
fun onUserDataClick(userId: UserId)
|
||||||
fun onPermalinkClick(data: PermalinkData)
|
fun onPermalinkClick(data: PermalinkData)
|
||||||
|
|
@ -120,12 +120,12 @@ class MessagesNode @AssistedInject constructor(
|
||||||
callbacks.forEach { it.onRoomDetailsClick() }
|
callbacks.forEach { it.onRoomDetailsClick() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onEventClick(event: TimelineItem.Event): Boolean {
|
private fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean {
|
||||||
// Note: cannot use `callbacks.all { it.onEventClick(event) }` because:
|
// Note: cannot use `callbacks.all { it.onEventClick(event) }` because:
|
||||||
// - if callbacks is empty, it will return true and we want to return false.
|
// - if callbacks is empty, it will return true and we want to return false.
|
||||||
// - if a callback returns false, the other callback will not be invoked.
|
// - if a callback returns false, the other callback will not be invoked.
|
||||||
return callbacks.takeIf { it.isNotEmpty() }
|
return callbacks.takeIf { it.isNotEmpty() }
|
||||||
?.map { it.onEventClick(event) }
|
?.map { it.onEventClick(isLive, event) }
|
||||||
?.all { it }
|
?.all { it }
|
||||||
.orFalse()
|
.orFalse()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ fun MessagesView(
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onRoomDetailsClick: () -> Unit,
|
onRoomDetailsClick: () -> Unit,
|
||||||
onEventContentClick: (event: TimelineItem.Event) -> Boolean,
|
onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean,
|
||||||
onUserDataClick: (UserId) -> Unit,
|
onUserDataClick: (UserId) -> Unit,
|
||||||
onLinkClick: (String, Boolean) -> Unit,
|
onLinkClick: (String, Boolean) -> Unit,
|
||||||
onSendLocationClick: () -> Unit,
|
onSendLocationClick: () -> Unit,
|
||||||
|
|
@ -140,7 +140,7 @@ fun MessagesView(
|
||||||
|
|
||||||
fun onContentClick(event: TimelineItem.Event) {
|
fun onContentClick(event: TimelineItem.Event) {
|
||||||
Timber.v("onMessageClick= ${event.id}")
|
Timber.v("onMessageClick= ${event.id}")
|
||||||
val hideKeyboard = onEventContentClick(event)
|
val hideKeyboard = onEventContentClick(state.timelineState.isLive, event)
|
||||||
if (hideKeyboard) {
|
if (hideKeyboard) {
|
||||||
localView.hideKeyboard()
|
localView.hideKeyboard()
|
||||||
}
|
}
|
||||||
|
|
@ -535,7 +535,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = {},
|
onBackClick = {},
|
||||||
onRoomDetailsClick = {},
|
onRoomDetailsClick = {},
|
||||||
onEventContentClick = { false },
|
onEventContentClick = { _, _ -> false },
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
onLinkClick = { _, _ -> },
|
onLinkClick = { _, _ -> },
|
||||||
onSendLocationClick = {},
|
onSendLocationClick = {},
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview(
|
||||||
),
|
),
|
||||||
onBackClick = {},
|
onBackClick = {},
|
||||||
onRoomDetailsClick = {},
|
onRoomDetailsClick = {},
|
||||||
onEventContentClick = { false },
|
onEventContentClick = { _, _ -> false },
|
||||||
onUserDataClick = {},
|
onUserDataClick = {},
|
||||||
onLinkClick = { _, _ -> },
|
onLinkClick = { _, _ -> },
|
||||||
onSendLocationClick = {},
|
onSendLocationClick = {},
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
||||||
is AsyncData.Uninitialized, is AsyncData.Failure -> {
|
is AsyncData.Uninitialized, is AsyncData.Failure -> {
|
||||||
timelineStateFlow.emit(AsyncData.Loading())
|
timelineStateFlow.emit(AsyncData.Loading())
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
room.pinnedEventsTimeline()
|
room.createTimeline(onlyPinnedEvents = true)
|
||||||
}
|
}
|
||||||
.fold(
|
.fold(
|
||||||
{ timelineStateFlow.emit(AsyncData.Success(it)) },
|
{ timelineStateFlow.emit(AsyncData.Success(it)) },
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class TimelineController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
|
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
|
||||||
return room.timelineFocusedOnEvent(eventId)
|
return room.createTimeline(focusedOnEventId = eventId)
|
||||||
.onFailure {
|
.onFailure {
|
||||||
if (it is CancellationException) {
|
if (it is CancellationException) {
|
||||||
throw it
|
throw it
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
import io.element.android.tests.testutils.EnsureCalledOnceWithTwoParamsAndResult
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParamAndResult
|
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
||||||
|
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParamsAndResult
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
|
|
@ -129,8 +129,9 @@ class MessagesViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first()
|
val timelineItem = state.timelineState.timelineItems.first()
|
||||||
val callback = EnsureCalledOnceWithParam(
|
val callback = EnsureCalledOnceWithTwoParamsAndResult(
|
||||||
expectedParam = timelineItem,
|
expectedParam1 = true,
|
||||||
|
expectedParam2 = timelineItem,
|
||||||
result = true,
|
result = true,
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
|
|
@ -513,7 +514,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
|
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onEventClick: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(),
|
onEventClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithTwoParamsAndResult(),
|
||||||
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onLinkClick: (String, Boolean) -> Unit = EnsureNeverCalledWithTwoParams(),
|
onLinkClick: (String, Boolean) -> Unit = EnsureNeverCalledWithTwoParams(),
|
||||||
onSendLocationClick: () -> Unit = EnsureNeverCalled(),
|
onSendLocationClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - loading state`() = runTest {
|
fun `present - loading state`() = runTest {
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) }
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) }
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) }
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +125,7 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) }
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +160,7 @@ class PinnedMessagesBannerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - timeline failed`() = runTest {
|
fun `present - timeline failed`() = runTest {
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.failure(Exception()) }
|
createTimelineResult = { _, _, _ -> Result.failure(Exception()) }
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - timeline failure state`() = runTest {
|
fun `present - timeline failure state`() = runTest {
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.failure(RuntimeException()) },
|
createTimelineResult = { _, _, _ -> Result.failure(RuntimeException()) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -102,7 +102,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - empty state`() = runTest {
|
fun `present - empty state`() = runTest {
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -122,7 +122,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
fun `present - filled state`() = runTest {
|
fun `present - filled state`() = runTest {
|
||||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -149,7 +149,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -195,7 +195,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
}
|
}
|
||||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -224,7 +224,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
}
|
}
|
||||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
@ -253,7 +253,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
}
|
}
|
||||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
|
||||||
canRedactOwnResult = { Result.success(true) },
|
canRedactOwnResult = { Result.success(true) },
|
||||||
canRedactOtherResult = { Result.success(true) },
|
canRedactOtherResult = { Result.success(true) },
|
||||||
canUserPinUnpinResult = { Result.success(true) },
|
canUserPinUnpinResult = { Result.success(true) },
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class TimelineControllerTest {
|
||||||
val detachedTimeline = FakeTimeline(name = "detached")
|
val detachedTimeline = FakeTimeline(name = "detached")
|
||||||
val matrixRoom = FakeMatrixRoom(
|
val matrixRoom = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(matrixRoom)
|
val sut = TimelineController(matrixRoom)
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ class TimelineControllerTest {
|
||||||
var callNumber = 0
|
var callNumber = 0
|
||||||
val matrixRoom = FakeMatrixRoom(
|
val matrixRoom = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = {
|
createTimelineResult = { _, _, _ ->
|
||||||
callNumber++
|
callNumber++
|
||||||
when (callNumber) {
|
when (callNumber) {
|
||||||
1 -> Result.success(detachedTimeline1)
|
1 -> Result.success(detachedTimeline1)
|
||||||
|
|
@ -117,7 +117,7 @@ class TimelineControllerTest {
|
||||||
val detachedTimeline = FakeTimeline(name = "detached")
|
val detachedTimeline = FakeTimeline(name = "detached")
|
||||||
val matrixRoom = FakeMatrixRoom(
|
val matrixRoom = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(matrixRoom)
|
val sut = TimelineController(matrixRoom)
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
|
|
@ -167,7 +167,7 @@ class TimelineControllerTest {
|
||||||
}
|
}
|
||||||
val matrixRoom = FakeMatrixRoom(
|
val matrixRoom = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(matrixRoom)
|
val sut = TimelineController(matrixRoom)
|
||||||
sut.activeTimelineFlow().test {
|
sut.activeTimelineFlow().test {
|
||||||
|
|
@ -192,7 +192,7 @@ class TimelineControllerTest {
|
||||||
val detachedTimeline = FakeTimeline(name = "detached")
|
val detachedTimeline = FakeTimeline(name = "detached")
|
||||||
val matrixRoom = FakeMatrixRoom(
|
val matrixRoom = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
|
||||||
)
|
)
|
||||||
val sut = TimelineController(matrixRoom)
|
val sut = TimelineController(matrixRoom)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
)
|
)
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
liveTimeline = liveTimeline,
|
liveTimeline = liveTimeline,
|
||||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) },
|
||||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||||
)
|
)
|
||||||
val presenter = createTimelinePresenter(
|
val presenter = createTimelinePresenter(
|
||||||
|
|
@ -561,7 +561,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
liveTimeline = FakeTimeline(
|
liveTimeline = FakeTimeline(
|
||||||
timelineItems = flowOf(emptyList()),
|
timelineItems = flowOf(emptyList()),
|
||||||
),
|
),
|
||||||
timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
|
createTimelineResult = { _, _, _ -> Result.failure(Throwable("An error")) },
|
||||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -109,21 +109,17 @@ interface MatrixRoom : Closeable {
|
||||||
val liveTimeline: Timeline
|
val liveTimeline: Timeline
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new timeline, focused on the provided Event.
|
* Create a new timeline.
|
||||||
* Should not be used directly, see `TimelineController` to manage the various timelines.
|
* @param focusedOnEventId The event to focus on, if any. Note: if not null, and for regular timeline,
|
||||||
|
* this method should not be used directly, see `TimelineController` to manage the various timelines.
|
||||||
|
* @param onlyPinnedEvents True to get the timeline for pinned events only.
|
||||||
|
* @param onlyMedia True to get the timeline for media events only.
|
||||||
*/
|
*/
|
||||||
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
|
suspend fun createTimeline(
|
||||||
|
focusedOnEventId: EventId? = null,
|
||||||
/**
|
onlyPinnedEvents: Boolean = false,
|
||||||
* Create a new timeline for the pinned events of the room.
|
onlyMedia: Boolean = false,
|
||||||
*/
|
): Result<Timeline>
|
||||||
suspend fun pinnedEventsTimeline(): Result<Timeline>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new timeline for the media events of the room.
|
|
||||||
* @param eventId The event to focus on, if any.
|
|
||||||
*/
|
|
||||||
suspend fun mediaTimeline(eventId: EventId?): Result<Timeline>
|
|
||||||
|
|
||||||
fun destroy()
|
fun destroy()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,80 +214,72 @@ class RustMatrixRoom(
|
||||||
|
|
||||||
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId)
|
||||||
|
|
||||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> = withContext(roomDispatcher) {
|
override suspend fun createTimeline(
|
||||||
runCatching {
|
focusedOnEventId: EventId?,
|
||||||
innerRoom.timelineWithConfiguration(
|
onlyPinnedEvents: Boolean,
|
||||||
configuration = TimelineConfiguration(
|
onlyMedia: Boolean,
|
||||||
focus = TimelineFocus.Event(
|
|
||||||
eventId = eventId.value,
|
|
||||||
numContextEvents = 50u,
|
|
||||||
),
|
|
||||||
allowedMessageTypes = AllowedMessageTypes.All,
|
|
||||||
internalIdPrefix = "focus_$eventId",
|
|
||||||
dateDividerMode = DateDividerMode.DAILY,
|
|
||||||
)
|
|
||||||
).let { inner ->
|
|
||||||
createTimeline(inner, mode = Timeline.Mode.FOCUSED_ON_EVENT)
|
|
||||||
}
|
|
||||||
}.mapFailure {
|
|
||||||
it.toFocusEventException()
|
|
||||||
}.onFailure {
|
|
||||||
if (it is CancellationException) {
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> = withContext(roomDispatcher) {
|
|
||||||
runCatching {
|
|
||||||
innerRoom.timelineWithConfiguration(
|
|
||||||
configuration = TimelineConfiguration(
|
|
||||||
focus = TimelineFocus.PinnedEvents(
|
|
||||||
maxEventsToLoad = 100u,
|
|
||||||
maxConcurrentRequests = 10u,
|
|
||||||
),
|
|
||||||
allowedMessageTypes = AllowedMessageTypes.All,
|
|
||||||
internalIdPrefix = "pinned_events",
|
|
||||||
dateDividerMode = DateDividerMode.DAILY,
|
|
||||||
)
|
|
||||||
).let { inner ->
|
|
||||||
createTimeline(inner, mode = Timeline.Mode.PINNED_EVENTS)
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
if (it is CancellationException) {
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun mediaTimeline(
|
|
||||||
eventId: EventId?,
|
|
||||||
): Result<Timeline> = withContext(roomDispatcher) {
|
): Result<Timeline> = withContext(roomDispatcher) {
|
||||||
val focus = if (eventId != null) {
|
val focus = if (onlyPinnedEvents) {
|
||||||
|
TimelineFocus.PinnedEvents(
|
||||||
|
maxEventsToLoad = 100u,
|
||||||
|
maxConcurrentRequests = 10u,
|
||||||
|
)
|
||||||
|
} else if (focusedOnEventId != null) {
|
||||||
TimelineFocus.Event(
|
TimelineFocus.Event(
|
||||||
eventId = eventId.value,
|
eventId = focusedOnEventId.value,
|
||||||
numContextEvents = 50u,
|
numContextEvents = 50u,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TimelineFocus.Live
|
TimelineFocus.Live
|
||||||
}
|
}
|
||||||
|
val allowedMessageTypes = if (onlyMedia) {
|
||||||
|
AllowedMessageTypes.Only(
|
||||||
|
types = listOf(
|
||||||
|
RoomMessageEventMessageType.FILE,
|
||||||
|
RoomMessageEventMessageType.IMAGE,
|
||||||
|
RoomMessageEventMessageType.VIDEO,
|
||||||
|
RoomMessageEventMessageType.AUDIO,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AllowedMessageTypes.All
|
||||||
|
}
|
||||||
|
val internalIdPrefix = if (onlyPinnedEvents) {
|
||||||
|
"pinned_events"
|
||||||
|
} else if (focusedOnEventId != null) {
|
||||||
|
"focus_$focusedOnEventId"
|
||||||
|
} else if (onlyMedia) {
|
||||||
|
"MediaGallery_"
|
||||||
|
} else {
|
||||||
|
"live"
|
||||||
|
}
|
||||||
|
val dateDividerMode = if (onlyMedia) {
|
||||||
|
DateDividerMode.MONTHLY
|
||||||
|
} else {
|
||||||
|
DateDividerMode.DAILY
|
||||||
|
}
|
||||||
|
val mode = when {
|
||||||
|
onlyPinnedEvents -> Timeline.Mode.PINNED_EVENTS
|
||||||
|
focusedOnEventId != null -> Timeline.Mode.FOCUSED_ON_EVENT
|
||||||
|
onlyMedia -> Timeline.Mode.MEDIA
|
||||||
|
else -> Timeline.Mode.LIVE
|
||||||
|
}
|
||||||
runCatching {
|
runCatching {
|
||||||
innerRoom.timelineWithConfiguration(
|
innerRoom.timelineWithConfiguration(
|
||||||
configuration = TimelineConfiguration(
|
configuration = TimelineConfiguration(
|
||||||
focus = focus,
|
focus = focus,
|
||||||
allowedMessageTypes = AllowedMessageTypes.Only(
|
allowedMessageTypes = allowedMessageTypes,
|
||||||
types = listOf(
|
internalIdPrefix = internalIdPrefix,
|
||||||
RoomMessageEventMessageType.FILE,
|
dateDividerMode = dateDividerMode,
|
||||||
RoomMessageEventMessageType.IMAGE,
|
|
||||||
RoomMessageEventMessageType.VIDEO,
|
|
||||||
RoomMessageEventMessageType.AUDIO,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
internalIdPrefix = "MediaGallery_",
|
|
||||||
dateDividerMode = DateDividerMode.MONTHLY,
|
|
||||||
)
|
)
|
||||||
).let { inner ->
|
).let { inner ->
|
||||||
createTimeline(inner, mode = if (eventId != null) Timeline.Mode.FOCUSED_ON_EVENT else Timeline.Mode.MEDIA)
|
createTimeline(inner, mode = mode)
|
||||||
|
}
|
||||||
|
}.mapFailure {
|
||||||
|
if (focusedOnEventId != null) {
|
||||||
|
it.toFocusEventException()
|
||||||
|
} else {
|
||||||
|
it
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
if (it is CancellationException) {
|
if (it is CancellationException) {
|
||||||
|
|
|
||||||
|
|
@ -138,9 +138,7 @@ class FakeMatrixRoom(
|
||||||
private val leaveRoomLambda: () -> Result<Unit> = { lambdaError() },
|
private val leaveRoomLambda: () -> Result<Unit> = { lambdaError() },
|
||||||
private val updateMembersResult: () -> Unit = { lambdaError() },
|
private val updateMembersResult: () -> Unit = { lambdaError() },
|
||||||
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
|
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
|
||||||
private val timelineFocusedOnEventResult: (EventId) -> Result<Timeline> = { lambdaError() },
|
private val createTimelineResult: (EventId?, Boolean, Boolean) -> Result<Timeline> = { _, _, _ -> lambdaError() },
|
||||||
private val pinnedEventsTimelineResult: () -> Result<Timeline> = { lambdaError() },
|
|
||||||
private val mediaTimelineResult: (EventId?) -> Result<Timeline> = { lambdaError() },
|
|
||||||
private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
|
private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> },
|
||||||
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
|
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
|
||||||
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
|
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
|
||||||
|
|
@ -220,16 +218,12 @@ class FakeMatrixRoom(
|
||||||
_syncUpdateFlow.tryEmit(_syncUpdateFlow.value + 1)
|
_syncUpdateFlow.tryEmit(_syncUpdateFlow.value + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> = simulateLongTask {
|
override suspend fun createTimeline(
|
||||||
timelineFocusedOnEventResult(eventId)
|
focusedOnEventId: EventId?,
|
||||||
}
|
onlyPinnedEvents: Boolean,
|
||||||
|
onlyMedia: Boolean,
|
||||||
override suspend fun pinnedEventsTimeline(): Result<Timeline> = simulateLongTask {
|
): Result<Timeline> = simulateLongTask {
|
||||||
pinnedEventsTimelineResult()
|
createTimelineResult(focusedOnEventId, onlyPinnedEvents, onlyMedia)
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun mediaTimeline(eventId: EventId?): Result<Timeline> = simulateLongTask {
|
|
||||||
mediaTimelineResult(eventId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun subscribeToSync() {
|
override suspend fun subscribeToSync() {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.mediaviewer.api
|
package io.element.android.libraries.mediaviewer.api
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
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.bumble.appyx.core.plugin.Plugin
|
||||||
|
|
@ -14,6 +15,8 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
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.timeline.Timeline
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
interface MediaViewerEntryPoint : FeatureEntryPoint {
|
interface MediaViewerEntryPoint : FeatureEntryPoint {
|
||||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||||
|
|
@ -39,9 +42,14 @@ interface MediaViewerEntryPoint : FeatureEntryPoint {
|
||||||
val canShowInfo: Boolean,
|
val canShowInfo: Boolean,
|
||||||
) : NodeInputs
|
) : NodeInputs
|
||||||
|
|
||||||
enum class MediaViewerMode {
|
sealed interface MediaViewerMode : Parcelable {
|
||||||
SingleMedia,
|
@Parcelize
|
||||||
TimelineImagesAndVideos,
|
data object SingleMedia : MediaViewerMode
|
||||||
TimelineFilesAndAudios,
|
|
||||||
|
@Parcelize
|
||||||
|
data class TimelineImagesAndVideos(val timelineMode: Timeline.Mode) : MediaViewerMode
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class TimelineFilesAndAudios(val timelineMode: Timeline.Mode) : MediaViewerMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface FocusedTimelineMediaGalleryDataSourceFactory {
|
||||||
fun createFor(
|
fun createFor(
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
mediaItem: MediaItem.Event,
|
mediaItem: MediaItem.Event,
|
||||||
|
onlyPinnedEvents: Boolean,
|
||||||
): MediaGalleryDataSource
|
): MediaGalleryDataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
|
||||||
override fun createFor(
|
override fun createFor(
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
mediaItem: MediaItem.Event,
|
mediaItem: MediaItem.Event,
|
||||||
|
onlyPinnedEvents: Boolean,
|
||||||
): MediaGalleryDataSource {
|
): MediaGalleryDataSource {
|
||||||
return TimelineMediaGalleryDataSource(
|
return TimelineMediaGalleryDataSource(
|
||||||
room = room,
|
room = room,
|
||||||
|
|
@ -37,6 +39,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactory @Inject constructor(
|
||||||
room = room,
|
room = room,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
initialMediaItem = mediaItem,
|
initialMediaItem = mediaItem,
|
||||||
|
onlyPinnedEvents = onlyPinnedEvents,
|
||||||
),
|
),
|
||||||
timelineMediaItemsFactory = timelineMediaItemsFactory,
|
timelineMediaItemsFactory = timelineMediaItemsFactory,
|
||||||
mediaItemsPostProcessor = mediaItemsPostProcessor,
|
mediaItemsPostProcessor = mediaItemsPostProcessor,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class LiveMediaTimeline @Inject constructor(
|
||||||
override suspend fun getTimeline(): Result<Timeline> = mutex.withLock {
|
override suspend fun getTimeline(): Result<Timeline> = mutex.withLock {
|
||||||
val currentTimeline = timeline
|
val currentTimeline = timeline
|
||||||
if (currentTimeline == null) {
|
if (currentTimeline == null) {
|
||||||
room.mediaTimeline(null)
|
room.createTimeline(onlyMedia = true)
|
||||||
.onSuccess { timeline = it }
|
.onSuccess { timeline = it }
|
||||||
} else {
|
} else {
|
||||||
Result.success(currentTimeline)
|
Result.success(currentTimeline)
|
||||||
|
|
@ -58,14 +58,20 @@ class LiveMediaTimeline @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that will provide a media timeline that is focused on a particular event.
|
* A class that will provide a media timeline that is focused on a particular event.
|
||||||
|
* Optionally, the timeline will only contain the pinned events.
|
||||||
*/
|
*/
|
||||||
class FocusedMediaTimeline(
|
class FocusedMediaTimeline(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val eventId: EventId,
|
private val eventId: EventId,
|
||||||
|
private val onlyPinnedEvents: Boolean,
|
||||||
initialMediaItem: MediaItem.Event,
|
initialMediaItem: MediaItem.Event,
|
||||||
) : MediaTimeline {
|
) : MediaTimeline {
|
||||||
override suspend fun getTimeline(): Result<Timeline> {
|
override suspend fun getTimeline(): Result<Timeline> {
|
||||||
return room.mediaTimeline(eventId)
|
return room.createTimeline(
|
||||||
|
focusedOnEventId = eventId,
|
||||||
|
onlyPinnedEvents = onlyPinnedEvents,
|
||||||
|
onlyMedia = true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val cache = persistentListOf(
|
override val cache = persistentListOf(
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.overlay.operation.show
|
||||||
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
|
||||||
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.timeline.Timeline
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint
|
import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaInfo
|
import io.element.android.libraries.mediaviewer.api.MediaInfo
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
||||||
|
|
@ -96,9 +97,9 @@ class MediaGalleryRootNode @AssistedInject constructor(
|
||||||
val mode = when (item) {
|
val mode = when (item) {
|
||||||
is MediaItem.Audio,
|
is MediaItem.Audio,
|
||||||
is MediaItem.Voice,
|
is MediaItem.Voice,
|
||||||
is MediaItem.File -> MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios
|
is MediaItem.File -> MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(Timeline.Mode.MEDIA)
|
||||||
is MediaItem.Image,
|
is MediaItem.Image,
|
||||||
is MediaItem.Video -> MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos
|
is MediaItem.Video -> MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(Timeline.Mode.MEDIA)
|
||||||
}
|
}
|
||||||
overlay.show(
|
overlay.show(
|
||||||
NavTarget.MediaViewer(
|
NavTarget.MediaViewer(
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ class MediaViewerDataSource(
|
||||||
|
|
||||||
private val galleryMode = when (mode) {
|
private val galleryMode = when (mode) {
|
||||||
MediaViewerMode.SingleMedia,
|
MediaViewerMode.SingleMedia,
|
||||||
MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images
|
is MediaViewerMode.TimelineImagesAndVideos -> MediaGalleryMode.Images
|
||||||
MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files
|
is MediaViewerMode.TimelineFilesAndAudios -> MediaGalleryMode.Files
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map of sourceUrl to local media state
|
// Map of sourceUrl to local media state
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
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
|
||||||
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
||||||
import io.element.android.libraries.mediaviewer.impl.datasource.FocusedTimelineMediaGalleryDataSourceFactory
|
import io.element.android.libraries.mediaviewer.impl.datasource.FocusedTimelineMediaGalleryDataSourceFactory
|
||||||
|
|
@ -69,16 +70,40 @@ class MediaViewerNode @AssistedInject constructor(
|
||||||
// Should not happen
|
// Should not happen
|
||||||
timelineMediaGalleryDataSource
|
timelineMediaGalleryDataSource
|
||||||
} else {
|
} else {
|
||||||
// Does timelineMediaGalleryDataSource knows the eventId?
|
// Can we use a specific timeline?
|
||||||
val lastData = timelineMediaGalleryDataSource.getLastData().dataOrNull()
|
val timelineMode = when (val mode = inputs.mode) {
|
||||||
val isEventKnown = lastData?.hasEvent(eventId) == true
|
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> mode.timelineMode
|
||||||
if (isEventKnown) {
|
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> mode.timelineMode
|
||||||
timelineMediaGalleryDataSource
|
else -> null
|
||||||
} else {
|
}
|
||||||
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
when (timelineMode) {
|
||||||
eventId = eventId,
|
null -> timelineMediaGalleryDataSource
|
||||||
mediaItem = inputs.toMediaItem(),
|
Timeline.Mode.LIVE -> {
|
||||||
)
|
// Even if the timelineMediaGalleryDataSource does not know the eventId, the SDK will create the timeline faster
|
||||||
|
timelineMediaGalleryDataSource
|
||||||
|
}
|
||||||
|
Timeline.Mode.FOCUSED_ON_EVENT -> {
|
||||||
|
// Does timelineMediaGalleryDataSource knows the eventId?
|
||||||
|
val lastData = timelineMediaGalleryDataSource.getLastData().dataOrNull()
|
||||||
|
val isEventKnown = lastData?.hasEvent(eventId) == true
|
||||||
|
if (isEventKnown) {
|
||||||
|
timelineMediaGalleryDataSource
|
||||||
|
} else {
|
||||||
|
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
||||||
|
eventId = eventId,
|
||||||
|
mediaItem = inputs.toMediaItem(),
|
||||||
|
onlyPinnedEvents = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timeline.Mode.PINNED_EVENTS -> {
|
||||||
|
focusedTimelineMediaGalleryDataSourceFactory.createFor(
|
||||||
|
eventId = eventId,
|
||||||
|
mediaItem = inputs.toMediaItem(),
|
||||||
|
onlyPinnedEvents = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Timeline.Mode.MEDIA -> timelineMediaGalleryDataSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,8 +204,8 @@ class MediaViewerPresenter @AssistedInject constructor(
|
||||||
private fun showNoMoreItemsSnackbar() {
|
private fun showNoMoreItemsSnackbar() {
|
||||||
val messageResId = when (inputs.mode) {
|
val messageResId = when (inputs.mode) {
|
||||||
MediaViewerEntryPoint.MediaViewerMode.SingleMedia,
|
MediaViewerEntryPoint.MediaViewerMode.SingleMedia,
|
||||||
MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show
|
is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> R.string.screen_media_details_no_more_media_to_show
|
||||||
MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show
|
is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> R.string.screen_media_details_no_more_files_to_show
|
||||||
}
|
}
|
||||||
val message = SnackbarMessage(messageResId)
|
val message = SnackbarMessage(messageResId)
|
||||||
snackbarDispatcher.post(message)
|
snackbarDispatcher.post(message)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest {
|
||||||
val result = sut.createFor(
|
val result = sut.createFor(
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
mediaItem = aMediaItemImage(),
|
mediaItem = aMediaItemImage(),
|
||||||
|
onlyPinnedEvents = false,
|
||||||
)
|
)
|
||||||
assertThat(result).isInstanceOf(TimelineMediaGalleryDataSource::class.java)
|
assertThat(result).isInstanceOf(TimelineMediaGalleryDataSource::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,11 @@ class FocusedMediaTimelineTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTimeline returns the timeline provided by the room`() = runTest {
|
fun `getTimeline returns the timeline provided by the room`() = runTest {
|
||||||
val mediaTimelineResult = lambdaRecorder<EventId?, Result<Timeline>> {
|
val createTimelineResult = lambdaRecorder<EventId?, Boolean, Boolean, Result<Timeline>> { _, _, _ ->
|
||||||
Result.success(FakeTimeline())
|
Result.success(FakeTimeline())
|
||||||
}
|
}
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = mediaTimelineResult,
|
createTimelineResult = createTimelineResult,
|
||||||
)
|
)
|
||||||
val sut = createFocusedMediaTimeline(
|
val sut = createFocusedMediaTimeline(
|
||||||
room = room,
|
room = room,
|
||||||
|
|
@ -87,16 +87,38 @@ class FocusedMediaTimelineTest {
|
||||||
)
|
)
|
||||||
val timeline = sut.getTimeline()
|
val timeline = sut.getTimeline()
|
||||||
assertThat(timeline.isSuccess).isTrue()
|
assertThat(timeline.isSuccess).isTrue()
|
||||||
mediaTimelineResult.assertions().isCalledOnce().with(value(AN_EVENT_ID))
|
createTimelineResult.assertions().isCalledOnce()
|
||||||
|
.with(value(AN_EVENT_ID), value(false), value(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getTimeline returns the timeline provided by the room for pinned Events`() = runTest {
|
||||||
|
val createTimelineResult = lambdaRecorder<EventId?, Boolean, Boolean, Result<Timeline>> { _, _, _ ->
|
||||||
|
Result.success(FakeTimeline())
|
||||||
|
}
|
||||||
|
val room = FakeMatrixRoom(
|
||||||
|
createTimelineResult = createTimelineResult,
|
||||||
|
)
|
||||||
|
val sut = createFocusedMediaTimeline(
|
||||||
|
room = room,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
onlyPinnedEvent = true,
|
||||||
|
)
|
||||||
|
val timeline = sut.getTimeline()
|
||||||
|
assertThat(timeline.isSuccess).isTrue()
|
||||||
|
createTimelineResult.assertions().isCalledOnce()
|
||||||
|
.with(value(AN_EVENT_ID), value(true), value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFocusedMediaTimeline(
|
private fun createFocusedMediaTimeline(
|
||||||
room: MatrixRoom = FakeMatrixRoom(),
|
room: MatrixRoom = FakeMatrixRoom(),
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
initialMediaItem: MediaItem.Event = aMediaItemImage(),
|
initialMediaItem: MediaItem.Event = aMediaItemImage(),
|
||||||
|
onlyPinnedEvent: Boolean = false,
|
||||||
) = FocusedMediaTimeline(
|
) = FocusedMediaTimeline(
|
||||||
room = room,
|
room = room,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
initialMediaItem = initialMediaItem,
|
initialMediaItem = initialMediaItem,
|
||||||
|
onlyPinnedEvents = onlyPinnedEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,22 +28,23 @@ class LiveMediaTimelineTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTimeline returns the timeline provided by the room, then from cache`() = runTest {
|
fun `getTimeline returns the timeline provided by the room, then from cache`() = runTest {
|
||||||
val mediaTimelineResult = lambdaRecorder<EventId?, Result<Timeline>> {
|
val createTimelineResult = lambdaRecorder<EventId?, Boolean, Boolean, Result<Timeline>> { _, _, _ ->
|
||||||
Result.success(FakeTimeline())
|
Result.success(FakeTimeline())
|
||||||
}
|
}
|
||||||
val room = FakeMatrixRoom(
|
val room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = mediaTimelineResult,
|
createTimelineResult = createTimelineResult,
|
||||||
)
|
)
|
||||||
val sut = createLiveMediaTimeline(
|
val sut = createLiveMediaTimeline(
|
||||||
room = room,
|
room = room,
|
||||||
)
|
)
|
||||||
val timeline = sut.getTimeline()
|
val timeline = sut.getTimeline()
|
||||||
assertThat(timeline.isSuccess).isTrue()
|
assertThat(timeline.isSuccess).isTrue()
|
||||||
mediaTimelineResult.assertions().isCalledOnce().with(value(null))
|
createTimelineResult.assertions().isCalledOnce()
|
||||||
|
.with(value(null), value(false), value(true))
|
||||||
val timeline2 = sut.getTimeline()
|
val timeline2 = sut.getTimeline()
|
||||||
assertThat(timeline2.isSuccess).isTrue()
|
assertThat(timeline2.isSuccess).isTrue()
|
||||||
// No called another time
|
// No called another time
|
||||||
mediaTimelineResult.assertions().isCalledOnce()
|
createTimelineResult.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLiveMediaTimeline(
|
private fun createLiveMediaTimeline(
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
val fakeTimeline = FakeTimeline()
|
val fakeTimeline = FakeTimeline()
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(fakeTimeline) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -75,7 +75,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
runTest {
|
runTest {
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(fakeTimeline) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -112,7 +112,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
}
|
}
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(fakeTimeline) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -135,7 +135,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
}
|
}
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(fakeTimeline) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -154,7 +154,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
fun `test - failing to load timeline should emit an error`() = runTest {
|
fun `test - failing to load timeline should emit an error`() = runTest {
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.failure(AN_EXCEPTION) },
|
createTimelineResult = { _, _, _ -> Result.failure(AN_EXCEPTION) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -176,7 +176,7 @@ class TimelineMediaGalleryDataSourceTest {
|
||||||
)
|
)
|
||||||
val sut = createTimelineMediaGalleryDataSource(
|
val sut = createTimelineMediaGalleryDataSource(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(fakeTimeline) },
|
createTimelineResult = { _, _, _ -> Result.success(fakeTimeline) },
|
||||||
roomCoroutineScope = backgroundScope,
|
roomCoroutineScope = backgroundScope,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class MediaGalleryPresenterTest {
|
||||||
),
|
),
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
displayName = A_ROOM_NAME,
|
displayName = A_ROOM_NAME,
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
|
@ -71,7 +71,7 @@ class MediaGalleryPresenterTest {
|
||||||
val presenter = createMediaGalleryPresenter(
|
val presenter = createMediaGalleryPresenter(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
displayName = A_ROOM_NAME,
|
displayName = A_ROOM_NAME,
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
|
@ -101,7 +101,7 @@ class MediaGalleryPresenterTest {
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
sessionId = A_USER_ID,
|
sessionId = A_USER_ID,
|
||||||
displayName = A_ROOM_NAME,
|
displayName = A_ROOM_NAME,
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
canRedactOwnResult = { Result.success(canDeleteOwn) }
|
canRedactOwnResult = { Result.success(canDeleteOwn) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -144,7 +144,7 @@ class MediaGalleryPresenterTest {
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
sessionId = A_USER_ID,
|
sessionId = A_USER_ID,
|
||||||
displayName = A_ROOM_NAME,
|
displayName = A_ROOM_NAME,
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
canRedactOtherResult = { Result.success(canDeleteOther) }
|
canRedactOtherResult = { Result.success(canDeleteOther) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -177,7 +177,7 @@ class MediaGalleryPresenterTest {
|
||||||
val presenter = createMediaGalleryPresenter(
|
val presenter = createMediaGalleryPresenter(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
displayName = A_ROOM_NAME,
|
displayName = A_ROOM_NAME,
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
|
@ -244,7 +244,7 @@ class MediaGalleryPresenterTest {
|
||||||
)
|
)
|
||||||
val presenter = createMediaGalleryPresenter(
|
val presenter = createMediaGalleryPresenter(
|
||||||
room = FakeMatrixRoom(
|
room = FakeMatrixRoom(
|
||||||
mediaTimelineResult = { Result.success(FakeTimeline()) },
|
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
|
||||||
),
|
),
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class MediaViewerDataSourceTest {
|
||||||
fun `test dataFlow with data galleryMode image`() = runTest {
|
fun `test dataFlow with data galleryMode image`() = runTest {
|
||||||
val galleryDataSource = FakeMediaGalleryDataSource()
|
val galleryDataSource = FakeMediaGalleryDataSource()
|
||||||
val sut = createMediaViewerDataSource(
|
val sut = createMediaViewerDataSource(
|
||||||
mode = MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||||
galleryDataSource = galleryDataSource,
|
galleryDataSource = galleryDataSource,
|
||||||
)
|
)
|
||||||
sut.dataFlow().test {
|
sut.dataFlow().test {
|
||||||
|
|
@ -159,7 +159,7 @@ class MediaViewerDataSourceTest {
|
||||||
fun `test dataFlow with data galleryMode files`() = runTest {
|
fun `test dataFlow with data galleryMode files`() = runTest {
|
||||||
val galleryDataSource = FakeMediaGalleryDataSource()
|
val galleryDataSource = FakeMediaGalleryDataSource()
|
||||||
val sut = createMediaViewerDataSource(
|
val sut = createMediaViewerDataSource(
|
||||||
mode = MediaViewerMode.TimelineFilesAndAudios,
|
mode = MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||||
galleryDataSource = galleryDataSource,
|
galleryDataSource = galleryDataSource,
|
||||||
)
|
)
|
||||||
sut.dataFlow().test {
|
sut.dataFlow().test {
|
||||||
|
|
@ -265,7 +265,7 @@ class MediaViewerDataSourceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.createMediaViewerDataSource(
|
private fun TestScope.createMediaViewerDataSource(
|
||||||
mode: MediaViewerMode = MediaViewerMode.TimelineImagesAndVideos,
|
mode: MediaViewerMode = MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||||
galleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource(),
|
galleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource(),
|
||||||
mediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
|
mediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
|
||||||
localMediaFactory: LocalMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
localMediaFactory: LocalMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
||||||
|
|
|
||||||
|
|
@ -519,7 +519,7 @@ class MediaViewerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - snackbar displayed when there is no more items forward images and videos`() {
|
fun `present - snackbar displayed when there is no more items forward images and videos`() {
|
||||||
`present - snackbar displayed when there is no more items forward`(
|
`present - snackbar displayed when there is no more items forward`(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||||
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -527,7 +527,7 @@ class MediaViewerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - snackbar displayed when there is no more items forward files and audio`() {
|
fun `present - snackbar displayed when there is no more items forward files and audio`() {
|
||||||
`present - snackbar displayed when there is no more items forward`(
|
`present - snackbar displayed when there is no more items forward`(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||||
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -547,7 +547,7 @@ class MediaViewerPresenterTest {
|
||||||
awaitFirstItem()
|
awaitFirstItem()
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
|
|
@ -568,7 +568,7 @@ class MediaViewerPresenterTest {
|
||||||
// data source claims that there is no more items to load forward
|
// data source claims that there is no more items to load forward
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(anImage, aBackwardLoadingIndicator),
|
fileItems = persistentListOf(anImage, aBackwardLoadingIndicator),
|
||||||
|
|
@ -590,7 +590,7 @@ class MediaViewerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - snackbar displayed when there is no more items backward images and videos`() {
|
fun `present - snackbar displayed when there is no more items backward images and videos`() {
|
||||||
`present - snackbar displayed when there is no more items backward`(
|
`present - snackbar displayed when there is no more items backward`(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode = Timeline.Mode.MEDIA),
|
||||||
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
expectedSnackbarResId = R.string.screen_media_details_no_more_media_to_show,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -598,7 +598,7 @@ class MediaViewerPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - snackbar displayed when there is no more items backward files and audio`() {
|
fun `present - snackbar displayed when there is no more items backward files and audio`() {
|
||||||
`present - snackbar displayed when there is no more items backward`(
|
`present - snackbar displayed when there is no more items backward`(
|
||||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.MEDIA),
|
||||||
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
expectedSnackbarResId = R.string.screen_media_details_no_more_files_to_show,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -618,7 +618,7 @@ class MediaViewerPresenterTest {
|
||||||
awaitFirstItem()
|
awaitFirstItem()
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
|
|
@ -640,7 +640,7 @@ class MediaViewerPresenterTest {
|
||||||
// data source claims that there is no more items to load backward
|
// data source claims that there is no more items to load backward
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
if (mode == MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aForwardLoadingIndicator, anImage),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage),
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,27 @@ class EnsureCalledOnceWithTwoParams<T, U>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EnsureCalledOnceWithTwoParamsAndResult<T, U, R>(
|
||||||
|
private val expectedParam1: T,
|
||||||
|
private val expectedParam2: U,
|
||||||
|
private val result: R,
|
||||||
|
) : (T, U) -> R {
|
||||||
|
private var counter = 0
|
||||||
|
override fun invoke(p1: T, p2: U): R {
|
||||||
|
if (p1 != expectedParam1 || p2 != expectedParam2) {
|
||||||
|
throw AssertionError("Expected to be called with $expectedParam1 and $expectedParam2, but was called with $p1 and $p2")
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertSuccess() {
|
||||||
|
if (counter != 1) {
|
||||||
|
throw AssertionError("Expected to be called once, but was called $counter times")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for [<T, R> ensureCalledOnceWithParam] with Unit result.
|
* Shortcut for [<T, R> ensureCalledOnceWithParam] with Unit result.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,9 @@ class EnsureNeverCalledWithTwoParams<T, U> : (T, U) -> Unit {
|
||||||
lambdaError("Should not be called and is called with $p1 and $p2")
|
lambdaError("Should not be called and is called with $p1 and $p2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EnsureNeverCalledWithTwoParamsAndResult<T, U, R> : (T, U) -> R {
|
||||||
|
override fun invoke(p1: T, p2: U): R {
|
||||||
|
lambdaError("Should not be called and is called with $p1 and $p2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue