famedlysdk/lib/src/timeline.dart

188 lines
6.2 KiB
Dart
Raw Normal View History

2019-06-21 10:18:54 +00:00
/*
* Copyright (c) 2019 Zender & Kurtz GbR.
*
* Authors:
* Christian Pauly <krille@famedly.com>
* Marcel Radzio <mtrnord@famedly.com>
*
* This file is part of famedlysdk.
*
* famedlysdk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* famedlysdk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'event.dart';
import 'room.dart';
import 'user.dart';
import 'sync/event_update.dart';
2019-06-21 10:18:54 +00:00
typedef onTimelineUpdateCallback = void Function();
typedef onTimelineInsertCallback = void Function(int insertID);
2019-06-21 10:18:54 +00:00
/// Represents the timeline of a room. The callbacks [onUpdate], [onDelete],
/// [onInsert] and [onResort] will be triggered automatically. The initial
/// event list will be retreived when created by the [room.getTimeline] method.
class Timeline {
final Room room;
List<Event> events = [];
2019-06-25 10:06:26 +00:00
final onTimelineUpdateCallback onUpdate;
final onTimelineInsertCallback onInsert;
2019-06-21 10:18:54 +00:00
StreamSubscription<EventUpdate> sub;
2020-02-21 15:05:19 +00:00
StreamSubscription<String> sessionIdReceivedSub;
2019-09-30 09:21:57 +00:00
bool _requestingHistoryLock = false;
2019-06-21 10:18:54 +00:00
2019-11-29 11:12:04 +00:00
Map<String, Event> _eventCache = {};
/// Searches for the event in this timeline. If not
/// found, requests from the server. Requested events
/// are cached.
Future<Event> getEventById(String id) async {
for (int i = 0; i < events.length; i++) {
if (events[i].eventId == id) return events[i];
}
if (_eventCache.containsKey(id)) return _eventCache[id];
final Event requestedEvent = await room.getEventById(id);
if (requestedEvent == null) return null;
_eventCache[id] = requestedEvent;
return _eventCache[id];
}
2019-09-30 09:21:57 +00:00
Future<void> requestHistory(
{int historyCount = Room.DefaultHistoryCount}) async {
if (!_requestingHistoryLock) {
_requestingHistoryLock = true;
await room.requestHistory(
historyCount: historyCount,
onHistoryReceived: () {
if (room.prev_batch.isEmpty || room.prev_batch == null) events = [];
2019-09-30 09:21:57 +00:00
},
);
await Future.delayed(const Duration(seconds: 2));
2019-09-30 09:21:57 +00:00
_requestingHistoryLock = false;
}
}
2019-06-21 10:18:54 +00:00
Timeline({this.room, this.events, this.onUpdate, this.onInsert}) {
2020-01-02 14:09:49 +00:00
sub ??= room.client.onEvent.stream.listen(_handleEventUpdate);
2020-02-21 15:05:19 +00:00
sessionIdReceivedSub ??=
room.onSessionKeyReceived.stream.listen(_sessionKeyReceived);
}
/// Don't forget to call this before you dismiss this object!
void cancelSubscriptions() {
sub?.cancel();
sessionIdReceivedSub?.cancel();
}
void _sessionKeyReceived(String sessionId) {
bool decryptAtLeastOneEvent = false;
for (int i = 0; i < events.length; i++) {
if (events[i].type == EventTypes.Encrypted &&
events[i].messageType == MessageTypes.BadEncrypted &&
events[i].content["body"] == DecryptError.UNKNOWN_SESSION &&
events[i].content["session_id"] == sessionId) {
events[i] = events[i].decrypted;
if (events[i].type != EventTypes.Encrypted) {
decryptAtLeastOneEvent = true;
}
}
}
if (decryptAtLeastOneEvent) onUpdate();
2019-06-21 10:18:54 +00:00
}
2019-07-23 09:09:13 +00:00
int _findEvent({String event_id, String unsigned_txid}) {
int i;
for (i = 0; i < events.length; i++) {
2019-08-07 09:38:51 +00:00
if (events[i].eventId == event_id ||
(unsigned_txid != null && events[i].eventId == unsigned_txid)) break;
}
return i;
}
2019-06-21 10:18:54 +00:00
void _handleEventUpdate(EventUpdate eventUpdate) async {
try {
if (eventUpdate.roomID != room.id) return;
2019-06-21 10:18:54 +00:00
if (eventUpdate.type == "timeline" || eventUpdate.type == "history") {
2019-12-12 12:19:18 +00:00
// Redaction events are handled as modification for existing events.
if (eventUpdate.eventType == "m.room.redaction") {
final int eventId =
_findEvent(event_id: eventUpdate.content["redacts"]);
if (eventId != null) {
events[eventId]
.setRedactionEvent(Event.fromJson(eventUpdate.content, room));
}
} else if (eventUpdate.content["status"] == -2) {
int i = _findEvent(event_id: eventUpdate.content["event_id"]);
if (i < events.length) events.removeAt(i);
}
2019-06-26 14:36:34 +00:00
// Is this event already in the timeline?
2019-07-23 09:09:13 +00:00
else if (eventUpdate.content.containsKey("unsigned") &&
eventUpdate.content["unsigned"]["transaction_id"] is String) {
int i = _findEvent(
event_id: eventUpdate.content["event_id"],
unsigned_txid: eventUpdate.content.containsKey("unsigned")
? eventUpdate.content["unsigned"]["transaction_id"]
: null);
2019-06-26 14:36:34 +00:00
if (i < events.length) {
events[i] = Event.fromJson(eventUpdate.content, room);
}
} else {
Event newEvent;
User senderUser = await room.client.store
2019-06-26 14:36:34 +00:00
?.getUser(matrixID: eventUpdate.content["sender"], room: room);
if (senderUser != null) {
eventUpdate.content["displayname"] = senderUser.displayName;
eventUpdate.content["avatar_url"] = senderUser.avatarUrl.mxc;
2019-06-26 14:36:34 +00:00
}
2019-06-21 10:18:54 +00:00
2019-08-07 09:38:51 +00:00
newEvent = Event.fromJson(eventUpdate.content, room);
2019-06-21 10:18:54 +00:00
2019-09-30 09:21:57 +00:00
if (eventUpdate.type == "history" &&
events.indexWhere(
(e) => e.eventId == eventUpdate.content["event_id"]) !=
-1) return;
2019-06-26 14:36:34 +00:00
events.insert(0, newEvent);
if (onInsert != null) onInsert(0);
}
2019-06-21 10:18:54 +00:00
}
2019-06-21 11:30:39 +00:00
sortAndUpdate();
2019-06-21 10:18:54 +00:00
} catch (e) {
if (room.client.debug) {
print("[WARNING] (_handleEventUpdate) ${e.toString()}");
}
2019-06-21 10:18:54 +00:00
}
}
2019-06-21 11:30:39 +00:00
2019-10-01 09:39:15 +00:00
bool sortLock = false;
sort() {
2019-10-24 09:53:53 +00:00
if (sortLock || events.length < 2) return;
2019-10-01 09:39:15 +00:00
sortLock = true;
2020-01-02 14:09:49 +00:00
events?.sort((a, b) =>
b.time.millisecondsSinceEpoch.compareTo(a.time.millisecondsSinceEpoch));
2019-10-01 09:39:15 +00:00
sortLock = false;
}
sortAndUpdate() {
sort();
if (onUpdate != null) onUpdate();
2019-06-21 11:30:39 +00:00
}
2019-06-21 10:18:54 +00:00
}