Skip to content

Commit 2e9a158

Browse files
committed
Update Matrix Room API and allow media swipe on pinned event only.
1 parent 728a2c1 commit 2e9a158

File tree

30 files changed

+269
-182
lines changed

30 files changed

+269
-182
lines changed

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt

+19-9
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
7171
import io.element.android.libraries.matrix.api.room.MatrixRoom
7272
import io.element.android.libraries.matrix.api.room.alias.matches
7373
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
74+
import io.element.android.libraries.matrix.api.timeline.Timeline
7475
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
7576
import io.element.android.libraries.matrix.ui.messages.LocalRoomMemberProfilesCache
7677
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
@@ -187,8 +188,11 @@ class MessagesFlowNode @AssistedInject constructor(
187188
callbacks.forEach { it.onRoomDetailsClick() }
188189
}
189190

190-
override fun onEventClick(event: TimelineItem.Event): Boolean {
191-
return processEventClick(event)
191+
override fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean {
192+
return processEventClick(
193+
timelineMode = if (isLive) Timeline.Mode.LIVE else Timeline.Mode.FOCUSED_ON_EVENT,
194+
event = event,
195+
)
192196
}
193197

194198
override fun onPreviewAttachments(attachments: ImmutableList<Attachment>) {
@@ -316,7 +320,10 @@ class MessagesFlowNode @AssistedInject constructor(
316320
NavTarget.PinnedMessagesList -> {
317321
val callback = object : PinnedMessagesListNode.Callback {
318322
override fun onEventClick(event: TimelineItem.Event) {
319-
processEventClick(event)
323+
processEventClick(
324+
timelineMode = Timeline.Mode.PINNED_EVENTS,
325+
event = event,
326+
)
320327
}
321328

322329
override fun onUserDataClick(userId: UserId) {
@@ -358,11 +365,14 @@ class MessagesFlowNode @AssistedInject constructor(
358365
callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) }
359366
}
360367

361-
private fun processEventClick(event: TimelineItem.Event): Boolean {
368+
private fun processEventClick(
369+
timelineMode: Timeline.Mode,
370+
event: TimelineItem.Event,
371+
): Boolean {
362372
val navTarget = when (event.content) {
363373
is TimelineItemImageContent -> {
364374
buildMediaViewerNavTarget(
365-
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
375+
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
366376
event = event,
367377
content = event.content,
368378
mediaSource = event.content.mediaSource,
@@ -374,7 +384,7 @@ class MessagesFlowNode @AssistedInject constructor(
374384
if encrypted on certain bridges */
375385
event.content.preferredMediaSource?.let { preferredMediaSource ->
376386
buildMediaViewerNavTarget(
377-
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
387+
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
378388
event = event,
379389
content = event.content,
380390
mediaSource = preferredMediaSource,
@@ -384,7 +394,7 @@ class MessagesFlowNode @AssistedInject constructor(
384394
}
385395
is TimelineItemVideoContent -> {
386396
buildMediaViewerNavTarget(
387-
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos,
397+
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos(timelineMode),
388398
event = event,
389399
content = event.content,
390400
mediaSource = event.content.mediaSource,
@@ -393,7 +403,7 @@ class MessagesFlowNode @AssistedInject constructor(
393403
}
394404
is TimelineItemFileContent -> {
395405
buildMediaViewerNavTarget(
396-
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
406+
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
397407
event = event,
398408
content = event.content,
399409
mediaSource = event.content.mediaSource,
@@ -402,7 +412,7 @@ class MessagesFlowNode @AssistedInject constructor(
402412
}
403413
is TimelineItemAudioContent -> {
404414
buildMediaViewerNavTarget(
405-
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios,
415+
mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode),
406416
event = event,
407417
content = event.content,
408418
mediaSource = event.content.mediaSource,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class MessagesNode @AssistedInject constructor(
8989

9090
interface Callback : Plugin {
9191
fun onRoomDetailsClick()
92-
fun onEventClick(event: TimelineItem.Event): Boolean
92+
fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean
9393
fun onPreviewAttachments(attachments: ImmutableList<Attachment>)
9494
fun onUserDataClick(userId: UserId)
9595
fun onPermalinkClick(data: PermalinkData)
@@ -120,12 +120,12 @@ class MessagesNode @AssistedInject constructor(
120120
callbacks.forEach { it.onRoomDetailsClick() }
121121
}
122122

123-
private fun onEventClick(event: TimelineItem.Event): Boolean {
123+
private fun onEventClick(isLive: Boolean, event: TimelineItem.Event): Boolean {
124124
// Note: cannot use `callbacks.all { it.onEventClick(event) }` because:
125125
// - if callbacks is empty, it will return true and we want to return false.
126126
// - if a callback returns false, the other callback will not be invoked.
127127
return callbacks.takeIf { it.isNotEmpty() }
128-
?.map { it.onEventClick(event) }
128+
?.map { it.onEventClick(isLive, event) }
129129
?.all { it }
130130
.orFalse()
131131
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ fun MessagesView(
109109
state: MessagesState,
110110
onBackClick: () -> Unit,
111111
onRoomDetailsClick: () -> Unit,
112-
onEventContentClick: (event: TimelineItem.Event) -> Boolean,
112+
onEventContentClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean,
113113
onUserDataClick: (UserId) -> Unit,
114114
onLinkClick: (String, Boolean) -> Unit,
115115
onSendLocationClick: () -> Unit,
@@ -140,7 +140,7 @@ fun MessagesView(
140140

141141
fun onContentClick(event: TimelineItem.Event) {
142142
Timber.v("onMessageClick= ${event.id}")
143-
val hideKeyboard = onEventContentClick(event)
143+
val hideKeyboard = onEventContentClick(state.timelineState.isLive, event)
144144
if (hideKeyboard) {
145145
localView.hideKeyboard()
146146
}
@@ -535,7 +535,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
535535
state = state,
536536
onBackClick = {},
537537
onRoomDetailsClick = {},
538-
onEventContentClick = { false },
538+
onEventContentClick = { _, _ -> false },
539539
onUserDataClick = {},
540540
onLinkClick = { _, _ -> },
541541
onSendLocationClick = {},

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview(
3333
),
3434
onBackClick = {},
3535
onRoomDetailsClick = {},
36-
onEventContentClick = { false },
36+
onEventContentClick = { _, _ -> false },
3737
onUserDataClick = {},
3838
onLinkClick = { _, _ -> },
3939
onSendLocationClick = {},

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class PinnedEventsTimelineProvider @Inject constructor(
104104
is AsyncData.Uninitialized, is AsyncData.Failure -> {
105105
timelineStateFlow.emit(AsyncData.Loading())
106106
withContext(dispatchers.io) {
107-
room.pinnedEventsTimeline()
107+
room.createTimeline(onlyPinnedEvents = true)
108108
}
109109
.fold(
110110
{ timelineStateFlow.emit(AsyncData.Success(it)) },

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class TimelineController @Inject constructor(
6464
}
6565

6666
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
67-
return room.timelineFocusedOnEvent(eventId)
67+
return room.createTimeline(focusedOnEventId = eventId)
6868
.onFailure {
6969
if (it is CancellationException) {
7070
throw it

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt

+6-5
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ import io.element.android.libraries.matrix.api.core.UserId
5454
import io.element.android.libraries.matrix.test.AN_EVENT_ID
5555
import io.element.android.libraries.testtags.TestTags
5656
import io.element.android.libraries.ui.strings.CommonStrings
57-
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
57+
import io.element.android.tests.testutils.EnsureCalledOnceWithTwoParamsAndResult
5858
import io.element.android.tests.testutils.EnsureNeverCalled
5959
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
60-
import io.element.android.tests.testutils.EnsureNeverCalledWithParamAndResult
6160
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
61+
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParamsAndResult
6262
import io.element.android.tests.testutils.EventsRecorder
6363
import io.element.android.tests.testutils.clickOn
6464
import io.element.android.tests.testutils.ensureCalledOnce
@@ -129,8 +129,9 @@ class MessagesViewTest {
129129
eventSink = eventsRecorder
130130
)
131131
val timelineItem = state.timelineState.timelineItems.first()
132-
val callback = EnsureCalledOnceWithParam(
133-
expectedParam = timelineItem,
132+
val callback = EnsureCalledOnceWithTwoParamsAndResult(
133+
expectedParam1 = true,
134+
expectedParam2 = timelineItem,
134135
result = true,
135136
)
136137
rule.setMessagesView(
@@ -513,7 +514,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
513514
state: MessagesState,
514515
onBackClick: () -> Unit = EnsureNeverCalled(),
515516
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
516-
onEventClick: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(),
517+
onEventClick: (isLive: Boolean, event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithTwoParamsAndResult(),
517518
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
518519
onLinkClick: (String, Boolean) -> Unit = EnsureNeverCalledWithTwoParams(),
519520
onSendLocationClick: () -> Unit = EnsureNeverCalled(),

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class PinnedMessagesBannerPresenterTest {
5555
@Test
5656
fun `present - loading state`() = runTest {
5757
val room = FakeMatrixRoom(
58-
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) }
58+
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) }
5959
).apply {
6060
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
6161
}
@@ -86,7 +86,7 @@ class PinnedMessagesBannerPresenterTest {
8686
)
8787
)
8888
val room = FakeMatrixRoom(
89-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
89+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) }
9090
).apply {
9191
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
9292
}
@@ -125,7 +125,7 @@ class PinnedMessagesBannerPresenterTest {
125125
)
126126
)
127127
val room = FakeMatrixRoom(
128-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }
128+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) }
129129
).apply {
130130
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2)))
131131
}
@@ -160,7 +160,7 @@ class PinnedMessagesBannerPresenterTest {
160160
@Test
161161
fun `present - timeline failed`() = runTest {
162162
val room = FakeMatrixRoom(
163-
pinnedEventsTimelineResult = { Result.failure(Exception()) }
163+
createTimelineResult = { _, _, _ -> Result.failure(Exception()) }
164164
).apply {
165165
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
166166
}

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class PinnedMessagesListPresenterTest {
8383
@Test
8484
fun `present - timeline failure state`() = runTest {
8585
val room = FakeMatrixRoom(
86-
pinnedEventsTimelineResult = { Result.failure(RuntimeException()) },
86+
createTimelineResult = { _, _, _ -> Result.failure(RuntimeException()) },
8787
canRedactOwnResult = { Result.success(true) },
8888
canRedactOtherResult = { Result.success(true) },
8989
canUserPinUnpinResult = { Result.success(true) },
@@ -102,7 +102,7 @@ class PinnedMessagesListPresenterTest {
102102
@Test
103103
fun `present - empty state`() = runTest {
104104
val room = FakeMatrixRoom(
105-
pinnedEventsTimelineResult = { Result.success(FakeTimeline()) },
105+
createTimelineResult = { _, _, _ -> Result.success(FakeTimeline()) },
106106
canRedactOwnResult = { Result.success(true) },
107107
canRedactOtherResult = { Result.success(true) },
108108
canUserPinUnpinResult = { Result.success(true) },
@@ -122,7 +122,7 @@ class PinnedMessagesListPresenterTest {
122122
fun `present - filled state`() = runTest {
123123
val pinnedEventsTimeline = createPinnedMessagesTimeline()
124124
val room = FakeMatrixRoom(
125-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
125+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
126126
canRedactOwnResult = { Result.success(true) },
127127
canRedactOtherResult = { Result.success(true) },
128128
canUserPinUnpinResult = { Result.success(true) },
@@ -149,7 +149,7 @@ class PinnedMessagesListPresenterTest {
149149
val pinnedEventsTimeline = createPinnedMessagesTimeline()
150150
val analyticsService = FakeAnalyticsService()
151151
val room = FakeMatrixRoom(
152-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
152+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
153153
canRedactOwnResult = { Result.success(true) },
154154
canRedactOtherResult = { Result.success(true) },
155155
canUserPinUnpinResult = { Result.success(true) },
@@ -195,7 +195,7 @@ class PinnedMessagesListPresenterTest {
195195
}
196196
val pinnedEventsTimeline = createPinnedMessagesTimeline()
197197
val room = FakeMatrixRoom(
198-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
198+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
199199
canRedactOwnResult = { Result.success(true) },
200200
canRedactOtherResult = { Result.success(true) },
201201
canUserPinUnpinResult = { Result.success(true) },
@@ -224,7 +224,7 @@ class PinnedMessagesListPresenterTest {
224224
}
225225
val pinnedEventsTimeline = createPinnedMessagesTimeline()
226226
val room = FakeMatrixRoom(
227-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
227+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
228228
canRedactOwnResult = { Result.success(true) },
229229
canRedactOtherResult = { Result.success(true) },
230230
canUserPinUnpinResult = { Result.success(true) },
@@ -253,7 +253,7 @@ class PinnedMessagesListPresenterTest {
253253
}
254254
val pinnedEventsTimeline = createPinnedMessagesTimeline()
255255
val room = FakeMatrixRoom(
256-
pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) },
256+
createTimelineResult = { _, _, _ -> Result.success(pinnedEventsTimeline) },
257257
canRedactOwnResult = { Result.success(true) },
258258
canRedactOtherResult = { Result.success(true) },
259259
canUserPinUnpinResult = { Result.success(true) },

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class TimelineControllerTest {
3131
val detachedTimeline = FakeTimeline(name = "detached")
3232
val matrixRoom = FakeMatrixRoom(
3333
liveTimeline = liveTimeline,
34-
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
34+
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
3535
)
3636
val sut = TimelineController(matrixRoom)
3737

@@ -63,7 +63,7 @@ class TimelineControllerTest {
6363
var callNumber = 0
6464
val matrixRoom = FakeMatrixRoom(
6565
liveTimeline = liveTimeline,
66-
timelineFocusedOnEventResult = {
66+
createTimelineResult = { _, _, _ ->
6767
callNumber++
6868
when (callNumber) {
6969
1 -> Result.success(detachedTimeline1)
@@ -117,7 +117,7 @@ class TimelineControllerTest {
117117
val detachedTimeline = FakeTimeline(name = "detached")
118118
val matrixRoom = FakeMatrixRoom(
119119
liveTimeline = liveTimeline,
120-
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
120+
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
121121
)
122122
val sut = TimelineController(matrixRoom)
123123
sut.activeTimelineFlow().test {
@@ -167,7 +167,7 @@ class TimelineControllerTest {
167167
}
168168
val matrixRoom = FakeMatrixRoom(
169169
liveTimeline = liveTimeline,
170-
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
170+
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
171171
)
172172
val sut = TimelineController(matrixRoom)
173173
sut.activeTimelineFlow().test {
@@ -192,7 +192,7 @@ class TimelineControllerTest {
192192
val detachedTimeline = FakeTimeline(name = "detached")
193193
val matrixRoom = FakeMatrixRoom(
194194
liveTimeline = liveTimeline,
195-
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
195+
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) }
196196
)
197197
val sut = TimelineController(matrixRoom)
198198

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ import kotlin.time.Duration.Companion.seconds
483483
)
484484
val room = FakeMatrixRoom(
485485
liveTimeline = liveTimeline,
486-
timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
486+
createTimelineResult = { _, _, _ -> Result.success(detachedTimeline) },
487487
canUserSendMessageResult = { _, _ -> Result.success(true) },
488488
)
489489
val presenter = createTimelinePresenter(
@@ -561,7 +561,7 @@ import kotlin.time.Duration.Companion.seconds
561561
liveTimeline = FakeTimeline(
562562
timelineItems = flowOf(emptyList()),
563563
),
564-
timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
564+
createTimelineResult = { _, _, _ -> Result.failure(Throwable("An error")) },
565565
canUserSendMessageResult = { _, _ -> Result.success(true) },
566566
)
567567
)

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt

+11-15
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,17 @@ interface MatrixRoom : Closeable {
109109
val liveTimeline: Timeline
110110

111111
/**
112-
* Create a new timeline, focused on the provided Event.
113-
* Should not be used directly, see `TimelineController` to manage the various timelines.
114-
*/
115-
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
116-
117-
/**
118-
* Create a new timeline for the pinned events of the room.
119-
*/
120-
suspend fun pinnedEventsTimeline(): Result<Timeline>
121-
122-
/**
123-
* Create a new timeline for the media events of the room.
124-
* @param eventId The event to focus on, if any.
125-
*/
126-
suspend fun mediaTimeline(eventId: EventId?): Result<Timeline>
112+
* Create a new timeline.
113+
* @param focusedOnEventId The event to focus on, if any. Note: if not null, and for regular timeline,
114+
* this method should not be used directly, see `TimelineController` to manage the various timelines.
115+
* @param onlyPinnedEvents True to get the timeline for pinned events only.
116+
* @param onlyMedia True to get the timeline for media events only.
117+
*/
118+
suspend fun createTimeline(
119+
focusedOnEventId: EventId? = null,
120+
onlyPinnedEvents: Boolean = false,
121+
onlyMedia: Boolean = false,
122+
): Result<Timeline>
127123

128124
fun destroy()
129125

0 commit comments

Comments
 (0)