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');