Merge pull request #4274 from element-hq/feature/bma/mediaTimelineImprovment
Update Matrix Room API and allow media swipe on pinned event only.
This commit is contained in:
commit
86afffb4bc
31 changed files with 296 additions and 185 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.alias.matches
|
||||
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.ui.messages.LocalRoomMemberProfilesCache
|
||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||
|
|
@ -187,8 +188,11 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.onRoomDetailsClick() }
|
||||
}
|
||||
|
||||
override fun onEventClick(event: TimelineItem.Event): Boolean {
|
||||
return processEventClick(event)
|
||||
override fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean {
|
||||
return processEventClick(
|
||||
timelineMode = if (isLive) Timeline.Mode.LIVE else Timeline.Mode.FOCUSED_ON_EVENT,
|
||||
event = event,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>) {
|
||||
|
|
@ -316,7 +320,10 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
NavTarget.PinnedMessagesList -> {
|
||||
val callback = object : PinnedMessagesListNode.Callback {
|
||||
override fun onEventClick(event: TimelineItem.Event) {
|
||||
processEventClick(event)
|
||||
processEventClick(
|
||||
timelineMode = Timeline.Mode.PINNED_EVENTS,
|
||||
event = event,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUserDataClick(userId: UserId) {
|
||||
|
|
@ -358,11 +365,14 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
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) {
|
||||
is TimelineItemImageContent -> {
|
||||
buildMediaViewerNavTarget(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
|
|
@ -374,7 +384,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
if encrypted on certain bridges */
|
||||
event.content.preferredMediaSource?.let { preferredMediaSource ->
|
||||
buildMediaViewerNavTarget(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = preferredMediaSource,
|
||||
|
|
@ -384,7 +394,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
}
|
||||
is TimelineItemVideoContent -> {
|
||||
buildMediaViewerNavTarget(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
|
|
@ -393,7 +403,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
}
|
||||
is TimelineItemFileContent -> {
|
||||
buildMediaViewerNavTarget(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
|
|
@ -402,7 +412,7 @@ class MessagesFlowNode @AssistedInject constructor(
|
|||
}
|
||||
is TimelineItemAudioContent -> {
|
||||
buildMediaViewerNavTarget(
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
|
||||
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
|
||||
event = event,
|
||||
content = event.content,
|
||||
mediaSource = event.content.mediaSource,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
|
||||
interface Callback : Plugin {
|
||||
fun onRoomDetailsClick()
|
||||
fun onEventClick(event: TimelineItem.Event): Boolean
|
||||
fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean
|
||||
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
|
||||
fun onUserDataClick(userId: UserId)
|
||||
fun onPermalinkClick(data: PermalinkData)
|
||||
|
|
@ -120,12 +120,12 @@ class MessagesNode @AssistedInject constructor(
|
|||
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:
|
||||
// - 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.
|
||||
return callbacks.takeIf { it.isNotEmpty() }
|
||||
?.map { it.onEventClick(event) }
|
||||
?.map { it.onEventClick(isLive, event) }
|
||||
?.all { it }
|
||||
.orFalse()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ fun MessagesView(
|
|||
state: MessagesState,
|
||||
onBackClick: () -> Unit,
|
||||
onRoomDetailsClick: () -> Unit,
|
||||
onEventContentClick: (event: TimelineItem.Event) -> Boolean,
|
||||
onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean,
|
||||
onUserDataClick: (UserId) -> Unit,
|
||||
onLinkClick: (String, Boolean) -> Unit,
|
||||
onSendLocationClick: () -> Unit,
|
||||
|
|
@ -142,7 +142,7 @@ fun MessagesView(
|
|||
|
||||
fun onContentClick(event: TimelineItem.Event) {
|
||||
Timber.v("onMessageClick= ${event.id}")
|
||||
val hideKeyboard = onEventContentClick(event)
|
||||
val hideKeyboard = onEventContentClick(state.timelineState.isLive, event)
|
||||
if (hideKeyboard) {
|
||||
localView.hideKeyboard()
|
||||
}
|
||||
|
|
@ -544,7 +544,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
|
|||
state = state,
|
||||
onBackClick = {},
|
||||
onRoomDetailsClick = {},
|
||||
onEventContentClick = { false },
|
||||
onEventContentClick = { _, _ -> false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = { _, _ -> },
|
||||
onSendLocationClick = {},
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview(
|
|||
),
|
||||
onBackClick = {},
|
||||
onRoomDetailsClick = {},
|
||||
onEventContentClick = { false },
|
||||
onEventContentClick = { _, _ -> false },
|
||||
onUserDataClick = {},
|
||||
onLinkClick = { _, _ -> },
|
||||
onSendLocationClick = {},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.di.RoomScope
|
|||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
|
|
@ -104,7 +105,7 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
|||
is AsyncData.Uninitialized, is AsyncData.Failure -> {
|
||||
timelineStateFlow.emit(AsyncData.Loading())
|
||||
withContext(dispatchers.io) {
|
||||
room.pinnedEventsTimeline()
|
||||
room.createTimeline(CreateTimelineParams.PinnedOnly)
|
||||
}
|
||||
.fold(
|
||||
{ timelineStateFlow.emit(AsyncData.Success(it)) },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
|||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
|
|
@ -64,7 +65,7 @@ class TimelineController @Inject constructor(
|
|||
}
|
||||
|
||||
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
|
||||
return room.timelineFocusedOnEvent(eventId)
|
||||
return room.createTimeline(CreateTimelineParams.Focused(eventId))
|
||||
.onFailure {
|
||||
if (it is CancellationException) {
|
||||
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.testtags.TestTags
|
||||
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.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParamAndResult
|
||||
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.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
|
|
@ -129,8 +129,9 @@ class MessagesViewTest {
|
|||
eventSink = eventsRecorder
|
||||
)
|
||||
val timelineItem = state.timelineState.timelineItems.first()
|
||||
val callback = EnsureCalledOnceWithParam(
|
||||
expectedParam = timelineItem,
|
||||
val callback = EnsureCalledOnceWithTwoParamsAndResult(
|
||||
expectedParam1 = true,
|
||||
expectedParam2 = timelineItem,
|
||||
result = true,
|
||||
)
|
||||
rule.setMessagesView(
|
||||
|
|
@ -513,7 +514,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
|
|||
state: MessagesState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
|
||||
onEventClick: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(),
|
||||
onEventClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithTwoParamsAndResult(),
|
||||
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onLinkClick: (String, Boolean) -> Unit = EnsureNeverCalledWithTwoParams(),
|
||||
onSendLocationClick: () -> Unit = EnsureNeverCalled(),
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class PinnedMessagesBannerPresenterTest {
|
|||
@Test
|
||||
fun `present - loading state`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) }
|
||||
createTimelineResult = { Result.success(FakeTimeline()) }
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ class PinnedMessagesBannerPresenterTest {
|
|||
)
|
||||
)
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) }
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ class PinnedMessagesBannerPresenterTest {
|
|||
)
|
||||
)
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) }
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
|
||||
}
|
||||
|
|
@ -160,7 +160,7 @@ class PinnedMessagesBannerPresenterTest {
|
|||
@Test
|
||||
fun `present - timeline failed`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.failure(Exception()) }
|
||||
createTimelineResult = { Result.failure(Exception()) }
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class PinnedMessagesListPresenterTest {
|
|||
@Test
|
||||
fun `present - timeline failure state`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.failure(RuntimeException()) },
|
||||
createTimelineResult = { Result.failure(RuntimeException()) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -102,7 +102,7 @@ class PinnedMessagesListPresenterTest {
|
|||
@Test
|
||||
fun `present - empty state`() = runTest {
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) },
|
||||
createTimelineResult = { Result.success(FakeTimeline()) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -122,7 +122,7 @@ class PinnedMessagesListPresenterTest {
|
|||
fun `present - filled state`() = runTest {
|
||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -149,7 +149,7 @@ class PinnedMessagesListPresenterTest {
|
|||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -195,7 +195,7 @@ class PinnedMessagesListPresenterTest {
|
|||
}
|
||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -224,7 +224,7 @@ class PinnedMessagesListPresenterTest {
|
|||
}
|
||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
@ -253,7 +253,7 @@ class PinnedMessagesListPresenterTest {
|
|||
}
|
||||
val pinnedEventsTimeline = createPinnedMessagesTimeline()
|
||||
val room = FakeMatrixRoom(
|
||||
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
createTimelineResult = { Result.success(pinnedEventsTimeline) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserPinUnpinResult = { Result.success(true) },
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class TimelineControllerTest {
|
|||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
createTimelineResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ class TimelineControllerTest {
|
|||
var callNumber = 0
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = {
|
||||
createTimelineResult = {
|
||||
callNumber++
|
||||
when (callNumber) {
|
||||
1 -> Result.success(detachedTimeline1)
|
||||
|
|
@ -117,7 +117,7 @@ class TimelineControllerTest {
|
|||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
createTimelineResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
val sut = TimelineController(matrixRoom)
|
||||
sut.activeTimelineFlow().test {
|
||||
|
|
@ -167,7 +167,7 @@ class TimelineControllerTest {
|
|||
}
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
createTimelineResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
val sut = TimelineController(matrixRoom)
|
||||
sut.activeTimelineFlow().test {
|
||||
|
|
@ -192,7 +192,7 @@ class TimelineControllerTest {
|
|||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
createTimelineResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
|
||||
createTimelineResult = { Result.success(detachedTimeline) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
)
|
||||
val presenter = createTimelinePresenter(
|
||||
|
|
@ -561,7 +561,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
liveTimeline = FakeTimeline(
|
||||
timelineItems = flowOf(emptyList()),
|
||||
),
|
||||
timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
|
||||
createTimelineResult = { Result.failure(Throwable("An error")) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue