feat: Add onInsert, onRemove and onUpdate cb to timeline

This makes it finally possible to
use Flutters AnimatedListView with
our Timeline class and in web we
can now update single elements
instead of the whole timeline
on every change which should
be quiet good for the
performance
This commit is contained in:
Krille Fear 2021-11-09 09:55:26 +01:00
parent 0e2542b172
commit e1f93637b3
2 changed files with 53 additions and 21 deletions

View File

@ -35,7 +35,9 @@ class Timeline {
final Map<String, Map<String, Set<Event>>> aggregatedEvents = {}; final Map<String, Map<String, Set<Event>>> aggregatedEvents = {};
final void Function()? onUpdate; final void Function()? onUpdate;
final void Function(int insertID)? onInsert; final void Function(int index)? onChange;
final void Function(int index)? onInsert;
final void Function(int index)? onRemove;
StreamSubscription<EventUpdate>? sub; StreamSubscription<EventUpdate>? sub;
StreamSubscription<SyncUpdate>? roomSub; StreamSubscription<SyncUpdate>? roomSub;
@ -86,6 +88,11 @@ class Timeline {
); );
if (eventsFromStore != null && eventsFromStore.isNotEmpty) { if (eventsFromStore != null && eventsFromStore.isNotEmpty) {
events.addAll(eventsFromStore); events.addAll(eventsFromStore);
final startIndex = events.length - eventsFromStore.length;
final endIndex = events.length;
for (var i = startIndex; i < endIndex; i++) {
onInsert?.call(i);
}
} else { } else {
Logs().v('No more events found in the store. Request from server...'); Logs().v('No more events found in the store. Request from server...');
await room.requestHistory( await room.requestHistory(
@ -102,9 +109,14 @@ class Timeline {
} }
} }
Timeline( Timeline({
{required this.room, List<Event>? events, this.onUpdate, this.onInsert}) required this.room,
: events = events ?? [] { List<Event>? events,
this.onUpdate,
this.onChange,
this.onInsert,
this.onRemove,
}) : events = events ?? [] {
sub = room.client.onEvent.stream.listen(_handleEventUpdate); sub = room.client.onEvent.stream.listen(_handleEventUpdate);
// If the timeline is limited we want to clear our events cache // If the timeline is limited we want to clear our events cache
roomSub = room.client.onSync.stream roomSub = room.client.onSync.stream
@ -142,6 +154,7 @@ class Timeline {
events[i].content['session_id'] == sessionId) { events[i].content['session_id'] == sessionId) {
events[i] = await encryption.decryptRoomEvent(room.id, events[i], events[i] = await encryption.decryptRoomEvent(room.id, events[i],
store: true); store: true);
onChange?.call(i);
if (events[i].type != EventTypes.Encrypted) { if (events[i].type != EventTypes.Encrypted) {
decryptAtLeastOneEvent = true; decryptAtLeastOneEvent = true;
} }
@ -249,19 +262,21 @@ class Timeline {
EventStatus.synced.intValue); EventStatus.synced.intValue);
// Redaction events are handled as modification for existing events. // Redaction events are handled as modification for existing events.
if (eventUpdate.content['type'] == EventTypes.Redaction) { if (eventUpdate.content['type'] == EventTypes.Redaction) {
final eventId = _findEvent(event_id: eventUpdate.content['redacts']); final index = _findEvent(event_id: eventUpdate.content['redacts']);
if (eventId < events.length) { if (index < events.length) {
removeAggregatedEvent(events[eventId]); removeAggregatedEvent(events[index]);
events[eventId].setRedactionEvent(Event.fromJson( events[index].setRedactionEvent(Event.fromJson(
eventUpdate.content, eventUpdate.content,
room, room,
)); ));
onChange?.call(index);
} }
} else if (status.isRemoved) { } else if (status.isRemoved) {
final i = _findEvent(event_id: eventUpdate.content['event_id']); final i = _findEvent(event_id: eventUpdate.content['event_id']);
if (i < events.length) { if (i < events.length) {
removeAggregatedEvent(events[i]); removeAggregatedEvent(events[i]);
events.removeAt(i); events.removeAt(i);
onRemove?.call(i);
} }
} else { } else {
final i = _findEvent( final i = _findEvent(
@ -283,6 +298,7 @@ class Timeline {
events[i].status = oldStatus; events[i].status = oldStatus;
} }
addAggregatedEvent(events[i]); addAggregatedEvent(events[i]);
onChange?.call(i);
} else { } else {
final newEvent = Event.fromJson( final newEvent = Event.fromJson(
eventUpdate.content, eventUpdate.content,
@ -293,14 +309,16 @@ class Timeline {
events.indexWhere( events.indexWhere(
(e) => e.eventId == eventUpdate.content['event_id']) != (e) => e.eventId == eventUpdate.content['event_id']) !=
-1) return; -1) return;
var index = events.length;
if (eventUpdate.type == EventUpdateType.history) { if (eventUpdate.type == EventUpdateType.history) {
events.add(newEvent); events.add(newEvent);
} else { } else {
events.insert(events.firstIndexWhereNotError, newEvent); index = events.firstIndexWhereNotError;
events.insert(index, newEvent);
} }
addAggregatedEvent(newEvent); addAggregatedEvent(newEvent);
onInsert?.call(0); onInsert?.call(index);
} }
} }
if (update && !_collectHistoryUpdates) { if (update && !_collectHistoryUpdates) {

View File

@ -34,6 +34,8 @@ void main() {
final testTimeStamp = DateTime.now().millisecondsSinceEpoch; final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
var updateCount = 0; var updateCount = 0;
final insertList = <int>[]; final insertList = <int>[];
final changeList = <int>[];
final removeList = <int>[];
var olmEnabled = true; var olmEnabled = true;
late Client client; late Client client;
@ -54,14 +56,15 @@ void main() {
room = Room( room = Room(
id: roomID, client: client, prev_batch: '1234', roomAccountData: {}); id: roomID, client: client, prev_batch: '1234', roomAccountData: {});
timeline = Timeline( timeline = Timeline(
room: room, room: room,
events: [], events: [],
onUpdate: () { onUpdate: () {
updateCount++; updateCount++;
}, },
onInsert: (int insertID) { onInsert: insertList.add,
insertList.add(insertID); onChange: changeList.add,
}); onRemove: removeList.add,
);
}); });
test('Create', () async { test('Create', () async {
@ -100,6 +103,8 @@ void main() {
expect(updateCount, 2); expect(updateCount, 2);
expect(insertList, [0, 0]); expect(insertList, [0, 0]);
expect(insertList.length, timeline.events.length); expect(insertList.length, timeline.events.length);
expect(changeList, []);
expect(removeList, []);
expect(timeline.events.length, 2); expect(timeline.events.length, 2);
expect(timeline.events[0].eventId, '1'); expect(timeline.events[0].eventId, '1');
expect(timeline.events[0].sender.id, '@alice:example.com'); expect(timeline.events[0].sender.id, '@alice:example.com');
@ -146,6 +151,8 @@ void main() {
expect(updateCount, 3); expect(updateCount, 3);
expect(insertList, [0, 0]); expect(insertList, [0, 0]);
expect(insertList.length, timeline.events.length); expect(insertList.length, timeline.events.length);
expect(changeList, [1]);
expect(removeList, []);
expect(timeline.events.length, 2); expect(timeline.events.length, 2);
expect(timeline.events[1].redacted, true); expect(timeline.events[1].redacted, true);
}); });
@ -211,8 +218,10 @@ void main() {
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
expect(updateCount, 13); expect(updateCount, 13);
expect(insertList, [0, 0, 0, 0, 0, 0, 0]); expect(insertList, [0, 0, 0, 0, 0, 1, 2]);
expect(insertList.length, timeline.events.length); expect(insertList.length, timeline.events.length);
expect(changeList, [1, 0, 0, 0, 1, 2]);
expect(removeList, []);
expect(timeline.events[0].status, EventStatus.error); expect(timeline.events[0].status, EventStatus.error);
expect(timeline.events[1].status, EventStatus.error); expect(timeline.events[1].status, EventStatus.error);
expect(timeline.events[2].status, EventStatus.error); expect(timeline.events[2].status, EventStatus.error);
@ -225,7 +234,9 @@ void main() {
expect(updateCount, 14); expect(updateCount, 14);
expect(insertList, [0, 0, 0, 0, 0, 0, 0]); expect(insertList, [0, 0, 0, 0, 0, 1, 2]);
expect(changeList, [1, 0, 0, 0, 1, 2]);
expect(removeList, [0]);
expect(timeline.events.length, 6); expect(timeline.events.length, 6);
expect(timeline.events[0].status, EventStatus.error); expect(timeline.events[0].status, EventStatus.error);
}); });
@ -270,7 +281,9 @@ void main() {
expect(updateCount, 17); expect(updateCount, 17);
expect(insertList, [0, 0, 0, 0, 0, 0, 0, 0]); expect(insertList, [0, 0, 0, 0, 0, 1, 2, 0]);
expect(changeList, [1, 0, 0, 0, 1, 2, 0, 0]);
expect(removeList, [0]);
expect(timeline.events.length, 1); expect(timeline.events.length, 1);
expect(timeline.events[0].status, EventStatus.sent); expect(timeline.events[0].status, EventStatus.sent);
}); });
@ -283,6 +296,7 @@ void main() {
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
expect(updateCount, 20); expect(updateCount, 20);
expect(insertList, [0, 0, 0, 0, 0, 1, 2, 0, 0, 1, 2]);
expect(timeline.events.length, 3); expect(timeline.events.length, 3);
expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org'); expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org');
expect(timeline.events[1].eventId, '2143273582443PhrSn:example.org'); expect(timeline.events[1].eventId, '2143273582443PhrSn:example.org');