From e1f93637b3a2c638b1b4074b04eba4346538febd Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Tue, 9 Nov 2021 09:55:26 +0100 Subject: [PATCH] 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 --- lib/src/timeline.dart | 38 ++++++++++++++++++++++++++++---------- test/timeline_test.dart | 36 +++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index b4cc82b7..47cead5e 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -35,7 +35,9 @@ class Timeline { final Map>> aggregatedEvents = {}; 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? sub; StreamSubscription? roomSub; @@ -86,6 +88,11 @@ class Timeline { ); if (eventsFromStore != null && eventsFromStore.isNotEmpty) { events.addAll(eventsFromStore); + final startIndex = events.length - eventsFromStore.length; + final endIndex = events.length; + for (var i = startIndex; i < endIndex; i++) { + onInsert?.call(i); + } } else { Logs().v('No more events found in the store. Request from server...'); await room.requestHistory( @@ -102,9 +109,14 @@ class Timeline { } } - Timeline( - {required this.room, List? events, this.onUpdate, this.onInsert}) - : events = events ?? [] { + Timeline({ + required this.room, + List? events, + this.onUpdate, + this.onChange, + this.onInsert, + this.onRemove, + }) : events = events ?? [] { sub = room.client.onEvent.stream.listen(_handleEventUpdate); // If the timeline is limited we want to clear our events cache roomSub = room.client.onSync.stream @@ -142,6 +154,7 @@ class Timeline { events[i].content['session_id'] == sessionId) { events[i] = await encryption.decryptRoomEvent(room.id, events[i], store: true); + onChange?.call(i); if (events[i].type != EventTypes.Encrypted) { decryptAtLeastOneEvent = true; } @@ -249,19 +262,21 @@ class Timeline { EventStatus.synced.intValue); // Redaction events are handled as modification for existing events. if (eventUpdate.content['type'] == EventTypes.Redaction) { - final eventId = _findEvent(event_id: eventUpdate.content['redacts']); - if (eventId < events.length) { - removeAggregatedEvent(events[eventId]); - events[eventId].setRedactionEvent(Event.fromJson( + final index = _findEvent(event_id: eventUpdate.content['redacts']); + if (index < events.length) { + removeAggregatedEvent(events[index]); + events[index].setRedactionEvent(Event.fromJson( eventUpdate.content, room, )); + onChange?.call(index); } } else if (status.isRemoved) { final i = _findEvent(event_id: eventUpdate.content['event_id']); if (i < events.length) { removeAggregatedEvent(events[i]); events.removeAt(i); + onRemove?.call(i); } } else { final i = _findEvent( @@ -283,6 +298,7 @@ class Timeline { events[i].status = oldStatus; } addAggregatedEvent(events[i]); + onChange?.call(i); } else { final newEvent = Event.fromJson( eventUpdate.content, @@ -293,14 +309,16 @@ class Timeline { events.indexWhere( (e) => e.eventId == eventUpdate.content['event_id']) != -1) return; + var index = events.length; if (eventUpdate.type == EventUpdateType.history) { events.add(newEvent); } else { - events.insert(events.firstIndexWhereNotError, newEvent); + index = events.firstIndexWhereNotError; + events.insert(index, newEvent); } addAggregatedEvent(newEvent); - onInsert?.call(0); + onInsert?.call(index); } } if (update && !_collectHistoryUpdates) { diff --git a/test/timeline_test.dart b/test/timeline_test.dart index 6e2be8b8..032da3b3 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -34,6 +34,8 @@ void main() { final testTimeStamp = DateTime.now().millisecondsSinceEpoch; var updateCount = 0; final insertList = []; + final changeList = []; + final removeList = []; var olmEnabled = true; late Client client; @@ -54,14 +56,15 @@ void main() { room = Room( id: roomID, client: client, prev_batch: '1234', roomAccountData: {}); timeline = Timeline( - room: room, - events: [], - onUpdate: () { - updateCount++; - }, - onInsert: (int insertID) { - insertList.add(insertID); - }); + room: room, + events: [], + onUpdate: () { + updateCount++; + }, + onInsert: insertList.add, + onChange: changeList.add, + onRemove: removeList.add, + ); }); test('Create', () async { @@ -100,6 +103,8 @@ void main() { expect(updateCount, 2); expect(insertList, [0, 0]); expect(insertList.length, timeline.events.length); + expect(changeList, []); + expect(removeList, []); expect(timeline.events.length, 2); expect(timeline.events[0].eventId, '1'); expect(timeline.events[0].sender.id, '@alice:example.com'); @@ -146,6 +151,8 @@ void main() { expect(updateCount, 3); expect(insertList, [0, 0]); expect(insertList.length, timeline.events.length); + expect(changeList, [1]); + expect(removeList, []); expect(timeline.events.length, 2); expect(timeline.events[1].redacted, true); }); @@ -211,8 +218,10 @@ void main() { await Future.delayed(Duration(milliseconds: 50)); 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(changeList, [1, 0, 0, 0, 1, 2]); + expect(removeList, []); expect(timeline.events[0].status, EventStatus.error); expect(timeline.events[1].status, EventStatus.error); expect(timeline.events[2].status, EventStatus.error); @@ -225,7 +234,9 @@ void main() { 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[0].status, EventStatus.error); }); @@ -270,7 +281,9 @@ void main() { 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[0].status, EventStatus.sent); }); @@ -283,6 +296,7 @@ void main() { await Future.delayed(Duration(milliseconds: 50)); expect(updateCount, 20); + expect(insertList, [0, 0, 0, 0, 0, 1, 2, 0, 0, 1, 2]); expect(timeline.events.length, 3); expect(timeline.events[0].eventId, '3143273582443PhrSn:example.org'); expect(timeline.events[1].eventId, '2143273582443PhrSn:example.org');