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 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<SyncUpdate>? 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<Event>? events, this.onUpdate, this.onInsert})
: events = events ?? [] {
Timeline({
required this.room,
List<Event>? 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) {

View File

@ -34,6 +34,8 @@ void main() {
final testTimeStamp = DateTime.now().millisecondsSinceEpoch;
var updateCount = 0;
final insertList = <int>[];
final changeList = <int>[];
final removeList = <int>[];
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');