Skip to content

Commit 8375c47

Browse files
committed
feat(sdk): Add pagination loading indicator as a virtual timeline item
1 parent 1b2387b commit 8375c47

File tree

6 files changed

+66
-6
lines changed

6 files changed

+66
-6
lines changed

bindings/matrix-sdk-ffi/src/timeline.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ impl TimelineItem {
159159
Some(VirtualTimelineItem::DayDivider { year: *year, month: *month, day: *day })
160160
}
161161
Item::Virtual(VItem::ReadMarker) => Some(VirtualTimelineItem::ReadMarker),
162+
Item::Virtual(VItem::LoadingIndicator) => Some(VirtualTimelineItem::LoadingIndicator),
162163
Item::Event(_) => None,
163164
}
164165
}
@@ -589,8 +590,12 @@ pub enum VirtualTimelineItem {
589590
/// A value between 1 and 31.
590591
day: u32,
591592
},
593+
592594
/// The user's own read marker.
593595
ReadMarker,
596+
597+
/// A loading indicator for a pagination request.
598+
LoadingIndicator,
594599
}
595600

596601
#[extension_trait]

crates/matrix-sdk/src/room/timeline/event_handler.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,31 +523,39 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
523523

524524
match position {
525525
TimelineItemPosition::Start => {
526+
// If there is a loading indicator at the top, check for / insert the day
527+
// divider at position 1 and the new event at 2 rather than 0 and 1.
528+
let offset =
529+
match self.timeline_items.first().and_then(|item| item.as_virtual()) {
530+
Some(VirtualTimelineItem::LoadingIndicator) => 1,
531+
_ => 0,
532+
};
533+
526534
// Check if the earliest day divider has the same date as this event.
527535
if let Some(VirtualTimelineItem::DayDivider { year, month, day }) =
528-
self.timeline_items.get(0).and_then(|item| item.as_virtual())
536+
self.timeline_items.get(offset).and_then(|item| item.as_virtual())
529537
{
530538
if let Some(day_divider_item) = maybe_create_day_divider_from_ymd(
531539
(*year, *month, *day),
532540
timestamp_to_ymd(*origin_server_ts),
533541
) {
534542
self.timeline_items.insert_cloned(
535-
0,
543+
offset,
536544
Arc::new(TimelineItem::Virtual(day_divider_item)),
537545
);
538546
}
539547
} else {
540548
// The list must always start with a day divider.
541549
let (year, month, day) = timestamp_to_ymd(*origin_server_ts);
542550
self.timeline_items.insert_cloned(
543-
0,
551+
offset,
544552
Arc::new(TimelineItem::Virtual(VirtualTimelineItem::day_divider(
545553
year, month, day,
546554
))),
547555
);
548556
}
549557

550-
self.timeline_items.insert_cloned(1, item)
558+
self.timeline_items.insert_cloned(offset + 1, item)
551559
}
552560
TimelineItemPosition::End => {
553561
// Check if the latest event has the same date as this event.

crates/matrix-sdk/src/room/timeline/inner.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,28 @@ impl TimelineInner {
145145
}
146146
}
147147

148+
#[instrument(skip_all)]
149+
pub(super) fn add_loading_indicator(&self) {
150+
let mut lock = self.items.lock_mut();
151+
if lock.first().map_or(false, |item| item.is_loading_indicator()) {
152+
warn!("There is already a loading indicator");
153+
return;
154+
}
155+
156+
lock.insert_cloned(0, Arc::new(TimelineItem::loading_indicator()));
157+
}
158+
159+
#[instrument(skip_all)]
160+
pub(super) fn remove_loading_indicator(&self) {
161+
let mut lock = self.items.lock_mut();
162+
if !lock.first().map_or(false, |item| item.is_loading_indicator()) {
163+
warn!("There is no loading indicator");
164+
return;
165+
}
166+
167+
lock.remove(0);
168+
}
169+
148170
pub(super) async fn handle_fully_read(&self, raw: Raw<FullyReadEvent>) {
149171
let fully_read_event = match raw.deserialize() {
150172
Ok(ev) => ev.content.event_id,

crates/matrix-sdk/src/room/timeline/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ impl Timeline {
167167
#[instrument(skip(self), fields(room_id = %self.room.room_id()))]
168168
pub async fn paginate_backwards(&self, limit: UInt) -> Result<PaginationOutcome> {
169169
let mut start_lock = self.start_token.lock().await;
170+
self.inner.add_loading_indicator();
170171
let messages = self
171172
.room
172173
.messages(assign!(MessagesOptions::backward(), {
@@ -181,6 +182,7 @@ impl Timeline {
181182
num_updates += self.inner.handle_back_paginated_event(room_ev, own_user_id).await;
182183
}
183184

185+
self.inner.remove_loading_indicator();
184186
let outcome = PaginationOutcome { more_messages: messages.end.is_some(), num_updates };
185187
*start_lock = messages.end;
186188

@@ -327,6 +329,14 @@ impl TimelineItem {
327329
_ => None,
328330
}
329331
}
332+
333+
fn loading_indicator() -> Self {
334+
Self::Virtual(VirtualTimelineItem::LoadingIndicator)
335+
}
336+
337+
fn is_loading_indicator(&self) -> bool {
338+
matches!(self, Self::Virtual(VirtualTimelineItem::LoadingIndicator))
339+
}
330340
}
331341

332342
/// The result of a successful pagination request.

crates/matrix-sdk/src/room/timeline/virtual_item.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ pub enum VirtualTimelineItem {
2828
/// A value between 1 and 31.
2929
day: u32,
3030
},
31+
3132
/// The user's own read marker.
3233
ReadMarker,
34+
35+
/// A loading indicator for a pagination request.
36+
LoadingIndicator,
3337
}
3438

3539
impl VirtualTimelineItem {

crates/matrix-sdk/tests/integration/room/timeline.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,10 +261,18 @@ async fn back_pagination() {
261261

262262
timeline.paginate_backwards(uint!(10)).await.unwrap();
263263

264-
let _day_divider = assert_matches!(
264+
let loading = assert_matches!(
265265
timeline_stream.next().await,
266266
Some(VecDiff::Push { value }) => value
267267
);
268+
assert_matches!(loading.as_virtual().unwrap(), VirtualTimelineItem::LoadingIndicator);
269+
270+
let day_divider = assert_matches!(
271+
timeline_stream.next().await,
272+
Some(VecDiff::Push { value }) => value
273+
);
274+
assert_matches!(day_divider.as_virtual().unwrap(), VirtualTimelineItem::DayDivider { .. });
275+
268276
let message = assert_matches!(
269277
timeline_stream.next().await,
270278
Some(VecDiff::Push { value }) => value
@@ -278,14 +286,17 @@ async fn back_pagination() {
278286

279287
let message = assert_matches!(
280288
timeline_stream.next().await,
281-
Some(VecDiff::InsertAt { index: 1, value }) => value
289+
Some(VecDiff::InsertAt { index: 2, value }) => value
282290
);
283291
let msg = assert_matches!(
284292
message.as_event().unwrap().content(),
285293
TimelineItemContent::Message(msg) => msg
286294
);
287295
let text = assert_matches!(msg.msgtype(), MessageType::Text(text) => text);
288296
assert_eq!(text.body, "the world is big");
297+
298+
// Removal of the loading indicator
299+
assert_matches!(timeline_stream.next().await, Some(VecDiff::RemoveAt { index: 0 }));
289300
}
290301

291302
#[async_test]

0 commit comments

Comments
 (0)