refactor: Change event status to enum
This commit is contained in:
parent
d5e5500ac5
commit
c6e0359522
|
|
@ -36,6 +36,7 @@ export 'src/utils/commands_extension.dart';
|
||||||
export 'src/utils/http_timeout.dart';
|
export 'src/utils/http_timeout.dart';
|
||||||
export 'src/client.dart';
|
export 'src/client.dart';
|
||||||
export 'src/event.dart';
|
export 'src/event.dart';
|
||||||
|
export 'src/event_status.dart';
|
||||||
export 'src/room.dart';
|
export 'src/room.dart';
|
||||||
export 'src/voip_content.dart';
|
export 'src/voip_content.dart';
|
||||||
export 'src/timeline.dart';
|
export 'src/timeline.dart';
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,17 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
|
||||||
import 'package:matrix/encryption/utils/ssss_cache.dart';
|
|
||||||
import 'package:matrix/encryption/utils/outbound_group_session.dart';
|
|
||||||
import 'package:matrix/encryption/utils/olm_session.dart';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:matrix/encryption/utils/olm_session.dart';
|
||||||
|
import 'package:matrix/encryption/utils/outbound_group_session.dart';
|
||||||
|
import 'package:matrix/encryption/utils/ssss_cache.dart';
|
||||||
|
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:matrix/src/event_status.dart';
|
||||||
|
import 'package:matrix/src/utils/queued_to_device_event.dart';
|
||||||
|
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
import 'package:matrix/src/utils/run_benchmarked.dart';
|
import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
|
|
||||||
/// This is a basic database for the Matrix SDK using the hive store. You need
|
/// This is a basic database for the Matrix SDK using the hive store. You need
|
||||||
|
|
@ -81,25 +82,43 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
late LazyBox _seenDeviceKeysBox;
|
late LazyBox _seenDeviceKeysBox;
|
||||||
|
|
||||||
String get _clientBoxName => '$name.box.client';
|
String get _clientBoxName => '$name.box.client';
|
||||||
|
|
||||||
String get _accountDataBoxName => '$name.box.account_data';
|
String get _accountDataBoxName => '$name.box.account_data';
|
||||||
|
|
||||||
String get _roomsBoxName => '$name.box.rooms';
|
String get _roomsBoxName => '$name.box.rooms';
|
||||||
|
|
||||||
String get _toDeviceQueueBoxName => '$name.box.to_device_queue';
|
String get _toDeviceQueueBoxName => '$name.box.to_device_queue';
|
||||||
|
|
||||||
String get _roomStateBoxName => '$name.box.room_states';
|
String get _roomStateBoxName => '$name.box.room_states';
|
||||||
|
|
||||||
String get _roomMembersBoxName => '$name.box.room_members';
|
String get _roomMembersBoxName => '$name.box.room_members';
|
||||||
|
|
||||||
String get _roomAccountDataBoxName => '$name.box.room_account_data';
|
String get _roomAccountDataBoxName => '$name.box.room_account_data';
|
||||||
|
|
||||||
String get _inboundGroupSessionsBoxName => '$name.box.inbound_group_session';
|
String get _inboundGroupSessionsBoxName => '$name.box.inbound_group_session';
|
||||||
|
|
||||||
String get _outboundGroupSessionsBoxName =>
|
String get _outboundGroupSessionsBoxName =>
|
||||||
'$name.box.outbound_group_session';
|
'$name.box.outbound_group_session';
|
||||||
|
|
||||||
String get _olmSessionsBoxName => '$name.box.olm_session';
|
String get _olmSessionsBoxName => '$name.box.olm_session';
|
||||||
|
|
||||||
String get _userDeviceKeysBoxName => '$name.box.user_device_keys';
|
String get _userDeviceKeysBoxName => '$name.box.user_device_keys';
|
||||||
|
|
||||||
String get _userDeviceKeysOutdatedBoxName =>
|
String get _userDeviceKeysOutdatedBoxName =>
|
||||||
'$name.box.user_device_keys_outdated';
|
'$name.box.user_device_keys_outdated';
|
||||||
|
|
||||||
String get _userCrossSigningKeysBoxName => '$name.box.cross_signing_keys';
|
String get _userCrossSigningKeysBoxName => '$name.box.cross_signing_keys';
|
||||||
|
|
||||||
String get _ssssCacheBoxName => '$name.box.ssss_cache';
|
String get _ssssCacheBoxName => '$name.box.ssss_cache';
|
||||||
|
|
||||||
String get _presencesBoxName => '$name.box.presences';
|
String get _presencesBoxName => '$name.box.presences';
|
||||||
|
|
||||||
String get _timelineFragmentsBoxName => '$name.box.timeline_fragments';
|
String get _timelineFragmentsBoxName => '$name.box.timeline_fragments';
|
||||||
|
|
||||||
String get _eventsBoxName => '$name.box.events';
|
String get _eventsBoxName => '$name.box.events';
|
||||||
|
|
||||||
String get _seenDeviceIdsBoxName => '$name.box.seen_device_ids';
|
String get _seenDeviceIdsBoxName => '$name.box.seen_device_ids';
|
||||||
|
|
||||||
String get _seenDeviceKeysBoxName => '$name.box.seen_device_keys';
|
String get _seenDeviceKeysBoxName => '$name.box.seen_device_keys';
|
||||||
|
|
||||||
final HiveCipher? encryptionCipher;
|
final HiveCipher? encryptionCipher;
|
||||||
|
|
@ -872,27 +891,34 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// calculate the status
|
// calculate the status
|
||||||
final newStatus = eventUpdate.content.tryGet<int>('status') ??
|
final newStatus = eventStatusFromInt(
|
||||||
|
eventUpdate.content.tryGet<int>('status') ??
|
||||||
eventUpdate.content
|
eventUpdate.content
|
||||||
.tryGetMap<String, dynamic>('unsigned')
|
.tryGetMap<String, dynamic>('unsigned')
|
||||||
?.tryGet<int>(messageSendingStatusKey) ??
|
?.tryGet<int>(messageSendingStatusKey) ??
|
||||||
2;
|
EventStatus.synced.intValue,
|
||||||
|
);
|
||||||
|
|
||||||
// Is this the response to a sending event which is already synced? Then
|
// Is this the response to a sending event which is already synced? Then
|
||||||
// there is nothing to do here.
|
// there is nothing to do here.
|
||||||
if (newStatus != 2 && prevEvent?.status == 2) {
|
if (!newStatus.isSynced &&
|
||||||
|
prevEvent != null &&
|
||||||
|
prevEvent.status.isSynced) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final status =
|
final status =
|
||||||
newStatus == -1 || prevEvent == null || prevEvent.status == null
|
newStatus.isError || prevEvent == null || prevEvent.status != null
|
||||||
? newStatus
|
? newStatus
|
||||||
: max(prevEvent.status, newStatus);
|
: latestEventStatus(
|
||||||
|
prevEvent.status,
|
||||||
|
newStatus,
|
||||||
|
);
|
||||||
|
|
||||||
// Add the status and the sort order to the content so it get stored
|
// Add the status and the sort order to the content so it get stored
|
||||||
eventUpdate.content['unsigned'] ??= <String, dynamic>{};
|
eventUpdate.content['unsigned'] ??= <String, dynamic>{};
|
||||||
eventUpdate.content['unsigned'][messageSendingStatusKey] =
|
eventUpdate.content['unsigned'][messageSendingStatusKey] =
|
||||||
eventUpdate.content['status'] = status;
|
eventUpdate.content['status'] = status.intValue;
|
||||||
|
|
||||||
// In case this event has sent from this account we have a transaction ID
|
// In case this event has sent from this account we have a transaction ID
|
||||||
final transactionId = eventUpdate.content
|
final transactionId = eventUpdate.content
|
||||||
|
|
@ -903,8 +929,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
eventUpdate.content);
|
eventUpdate.content);
|
||||||
|
|
||||||
// Update timeline fragments
|
// Update timeline fragments
|
||||||
final key =
|
final key = MultiKey(eventUpdate.roomID, status.isSent ? '' : 'SENDING')
|
||||||
MultiKey(eventUpdate.roomID, status >= 1 ? '' : 'SENDING').toString();
|
.toString();
|
||||||
|
|
||||||
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
|
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
|
||||||
|
|
||||||
if (!eventIds.contains(eventId)) {
|
if (!eventIds.contains(eventId)) {
|
||||||
|
|
@ -914,8 +941,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
eventIds.insert(0, eventId);
|
eventIds.insert(0, eventId);
|
||||||
}
|
}
|
||||||
await _timelineFragmentsBox.put(key, eventIds);
|
await _timelineFragmentsBox.put(key, eventIds);
|
||||||
} else if (status == 2 &&
|
} else if (status.isSynced &&
|
||||||
prevEvent?.status == 1 &&
|
prevEvent != null &&
|
||||||
|
prevEvent.status.isSent &&
|
||||||
eventUpdate.type != EventUpdateType.history) {
|
eventUpdate.type != EventUpdateType.history) {
|
||||||
// Status changes from 1 -> 2? Make sure event is correctly sorted.
|
// Status changes from 1 -> 2? Make sure event is correctly sorted.
|
||||||
eventIds.remove(eventId);
|
eventIds.remove(eventId);
|
||||||
|
|
@ -923,7 +951,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If event comes from server timeline, remove sending events with this ID
|
// If event comes from server timeline, remove sending events with this ID
|
||||||
if (status >= 1) {
|
if (status.isSent) {
|
||||||
final key = MultiKey(eventUpdate.roomID, 'SENDING').toString();
|
final key = MultiKey(eventUpdate.roomID, 'SENDING').toString();
|
||||||
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
|
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
|
||||||
final i = eventIds.indexWhere((id) => id == eventId);
|
final i = eventIds.indexWhere((id) => id == eventId);
|
||||||
|
|
@ -933,7 +961,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is there a transaction id? Then delete the event with this id.
|
// Is there a transaction id? Then delete the event with this id.
|
||||||
if (status != -1 && status != 0 && transactionId != null) {
|
if (!status.isError && !status.isSending && transactionId != null) {
|
||||||
await removeEvent(transactionId, eventUpdate.roomID);
|
await removeEvent(transactionId, eventUpdate.roomID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1348,12 +1376,14 @@ Map<String, dynamic> convertToJson(Map map) {
|
||||||
|
|
||||||
class MultiKey {
|
class MultiKey {
|
||||||
final List<String> parts;
|
final List<String> parts;
|
||||||
|
|
||||||
MultiKey(String key1, [String? key2, String? key3])
|
MultiKey(String key1, [String? key2, String? key3])
|
||||||
: parts = [
|
: parts = [
|
||||||
key1,
|
key1,
|
||||||
if (key2 != null) key2,
|
if (key2 != null) key2,
|
||||||
if (key3 != null) key3,
|
if (key3 != null) key3,
|
||||||
];
|
];
|
||||||
|
|
||||||
const MultiKey.byParts(this.parts);
|
const MultiKey.byParts(this.parts);
|
||||||
|
|
||||||
MultiKey.fromString(String multiKeyString)
|
MultiKey.fromString(String multiKeyString)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -23,12 +23,13 @@ import 'dart:typed_data';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
|
import 'event_status.dart';
|
||||||
import 'room.dart';
|
import 'room.dart';
|
||||||
|
import 'utils/crypto/encrypted_file.dart';
|
||||||
|
import 'utils/event_localizations.dart';
|
||||||
|
import 'utils/html_to_text.dart';
|
||||||
import 'utils/matrix_localizations.dart';
|
import 'utils/matrix_localizations.dart';
|
||||||
import 'utils/receipt.dart';
|
import 'utils/receipt.dart';
|
||||||
import 'utils/event_localizations.dart';
|
|
||||||
import 'utils/crypto/encrypted_file.dart';
|
|
||||||
import 'utils/html_to_text.dart';
|
|
||||||
|
|
||||||
abstract class RelationshipTypes {
|
abstract class RelationshipTypes {
|
||||||
static const String reply = 'm.in_reply_to';
|
static const String reply = 'm.in_reply_to';
|
||||||
|
|
@ -53,21 +54,9 @@ class Event extends MatrixEvent {
|
||||||
final Room room;
|
final Room room;
|
||||||
|
|
||||||
/// The status of this event.
|
/// The status of this event.
|
||||||
/// -1=ERROR
|
EventStatus status;
|
||||||
/// 0=SENDING
|
|
||||||
/// 1=SENT
|
|
||||||
/// 2=TIMELINE
|
|
||||||
/// 3=ROOM_STATE
|
|
||||||
int status;
|
|
||||||
|
|
||||||
static const int defaultStatus = 2;
|
static const EventStatus defaultStatus = EventStatus.synced;
|
||||||
static const Map<String, int> statusType = {
|
|
||||||
'error': -1,
|
|
||||||
'sending': 0,
|
|
||||||
'sent': 1,
|
|
||||||
'timeline': 2,
|
|
||||||
'roomState': 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Optional. The event that redacted this event, if any. Otherwise null.
|
/// Optional. The event that redacted this event, if any. Otherwise null.
|
||||||
Event get redactedBecause =>
|
Event get redactedBecause =>
|
||||||
|
|
@ -98,7 +87,7 @@ class Event extends MatrixEvent {
|
||||||
this.roomId = roomId ?? room?.id;
|
this.roomId = roomId ?? room?.id;
|
||||||
this.senderId = senderId;
|
this.senderId = senderId;
|
||||||
this.unsigned = unsigned;
|
this.unsigned = unsigned;
|
||||||
// synapse unfortunatley isn't following the spec and tosses the prev_content
|
// synapse unfortunately isn't following the spec and tosses the prev_content
|
||||||
// into the unsigned block.
|
// into the unsigned block.
|
||||||
// Currently we are facing a very strange bug in web which is impossible to debug.
|
// Currently we are facing a very strange bug in web which is impossible to debug.
|
||||||
// It may be because of this line so we put this in try-catch until we can fix it.
|
// It may be because of this line so we put this in try-catch until we can fix it.
|
||||||
|
|
@ -119,7 +108,7 @@ class Event extends MatrixEvent {
|
||||||
// Mark event as failed to send if status is `sending` and event is older
|
// Mark event as failed to send if status is `sending` and event is older
|
||||||
// than the timeout. This should not happen with the deprecated Moor
|
// than the timeout. This should not happen with the deprecated Moor
|
||||||
// database!
|
// database!
|
||||||
if (status == 0 && room?.client?.database != null) {
|
if (status.isSending && room?.client?.database != null) {
|
||||||
// Age of this event in milliseconds
|
// Age of this event in milliseconds
|
||||||
final age = DateTime.now().millisecondsSinceEpoch -
|
final age = DateTime.now().millisecondsSinceEpoch -
|
||||||
originServerTs.millisecondsSinceEpoch;
|
originServerTs.millisecondsSinceEpoch;
|
||||||
|
|
@ -128,7 +117,7 @@ class Event extends MatrixEvent {
|
||||||
// Update this event in database and open timelines
|
// Update this event in database and open timelines
|
||||||
final json = toJson();
|
final json = toJson();
|
||||||
json['unsigned'] ??= <String, dynamic>{};
|
json['unsigned'] ??= <String, dynamic>{};
|
||||||
json['unsigned'][messageSendingStatusKey] = -1;
|
json['unsigned'][messageSendingStatusKey] = EventStatus.error.intValue;
|
||||||
room.client.handleSync(SyncUpdate(nextBatch: '')
|
room.client.handleSync(SyncUpdate(nextBatch: '')
|
||||||
..rooms = (RoomsUpdate()
|
..rooms = (RoomsUpdate()
|
||||||
..join = (<String, JoinedRoomUpdate>{}..[room.id] =
|
..join = (<String, JoinedRoomUpdate>{}..[room.id] =
|
||||||
|
|
@ -154,7 +143,7 @@ class Event extends MatrixEvent {
|
||||||
factory Event.fromMatrixEvent(
|
factory Event.fromMatrixEvent(
|
||||||
MatrixEvent matrixEvent,
|
MatrixEvent matrixEvent,
|
||||||
Room room, {
|
Room room, {
|
||||||
int status,
|
EventStatus status,
|
||||||
}) =>
|
}) =>
|
||||||
Event(
|
Event(
|
||||||
status: status,
|
status: status,
|
||||||
|
|
@ -179,9 +168,9 @@ class Event extends MatrixEvent {
|
||||||
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
final prevContent = Event.getMapFromPayload(jsonPayload['prev_content']);
|
final prevContent = Event.getMapFromPayload(jsonPayload['prev_content']);
|
||||||
return Event(
|
return Event(
|
||||||
status: jsonPayload['status'] ??
|
status: eventStatusFromInt(jsonPayload['status'] ??
|
||||||
unsigned[messageSendingStatusKey] ??
|
unsigned[messageSendingStatusKey] ??
|
||||||
defaultStatus,
|
defaultStatus.intValue),
|
||||||
stateKey: jsonPayload['state_key'],
|
stateKey: jsonPayload['state_key'],
|
||||||
prevContent: prevContent,
|
prevContent: prevContent,
|
||||||
content: content,
|
content: content,
|
||||||
|
|
@ -301,10 +290,11 @@ class Event extends MatrixEvent {
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes this event if the status is < 1. This event will just be removed
|
/// Removes this event if the status is [sending], [error] or [removed].
|
||||||
/// from the database and the timelines. Returns false if not removed.
|
/// This event will just be removed from the database and the timelines.
|
||||||
|
/// Returns [false] if not removed.
|
||||||
Future<bool> remove() async {
|
Future<bool> remove() async {
|
||||||
if (status < 1) {
|
if (!status.isSent) {
|
||||||
await room.client.database?.removeEvent(eventId, room.id);
|
await room.client.database?.removeEvent(eventId, room.id);
|
||||||
|
|
||||||
room.client.onEvent.add(EventUpdate(
|
room.client.onEvent.add(EventUpdate(
|
||||||
|
|
@ -312,7 +302,7 @@ class Event extends MatrixEvent {
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
content: {
|
content: {
|
||||||
'event_id': eventId,
|
'event_id': eventId,
|
||||||
'status': -2,
|
'status': EventStatus.removed.intValue,
|
||||||
'content': {'body': 'Removed...'}
|
'content': {'body': 'Removed...'}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
@ -321,9 +311,9 @@ class Event extends MatrixEvent {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to send this event again. Only works with events of status -1.
|
/// Try to send this event again. Only works with events of `EventStatus.isError`.
|
||||||
Future<String> sendAgain({String txid}) async {
|
Future<String> sendAgain({String txid}) async {
|
||||||
if (status != -1) return null;
|
if (!status.isError) return null;
|
||||||
// we do not remove the event here. It will automatically be updated
|
// we do not remove the event here. It will automatically be updated
|
||||||
// in the `sendEvent` method to transition -1 -> 0 -> 1 -> 2
|
// in the `sendEvent` method to transition -1 -> 0 -> 1 -> 2
|
||||||
final newEventId = await room.sendEvent(
|
final newEventId = await room.sendEvent(
|
||||||
|
|
@ -382,7 +372,7 @@ class Event extends MatrixEvent {
|
||||||
/// Returns if a file events thumbnail is encrypted
|
/// Returns if a file events thumbnail is encrypted
|
||||||
bool get isThumbnailEncrypted => infoMap['thumbnail_file'] is Map;
|
bool get isThumbnailEncrypted => infoMap['thumbnail_file'] is Map;
|
||||||
|
|
||||||
/// Gets the mimetipe of the attachment of a file event, or a blank string if not present
|
/// Gets the mimetype of the attachment of a file event, or a blank string if not present
|
||||||
String get attachmentMimetype => infoMap['mimetype'] is String
|
String get attachmentMimetype => infoMap['mimetype'] is String
|
||||||
? infoMap['mimetype'].toLowerCase()
|
? infoMap['mimetype'].toLowerCase()
|
||||||
: (content['file'] is Map && content['file']['mimetype'] is String
|
: (content['file'] is Map && content['file']['mimetype'] is String
|
||||||
|
|
@ -397,16 +387,16 @@ class Event extends MatrixEvent {
|
||||||
? infoMap['thumbnail_file']['mimetype']
|
? infoMap['thumbnail_file']['mimetype']
|
||||||
: '');
|
: '');
|
||||||
|
|
||||||
/// Gets the underyling mxc url of an attachment of a file event, or null if not present
|
/// Gets the underlying mxc url of an attachment of a file event, or null if not present
|
||||||
Uri get attachmentMxcUrl => Uri.parse(
|
Uri get attachmentMxcUrl => Uri.parse(
|
||||||
isAttachmentEncrypted ? content['file']['url'] : content['url']);
|
isAttachmentEncrypted ? content['file']['url'] : content['url']);
|
||||||
|
|
||||||
/// Gets the underyling mxc url of a thumbnail of a file event, or null if not present
|
/// Gets the underlying mxc url of a thumbnail of a file event, or null if not present
|
||||||
Uri get thumbnailMxcUrl => Uri.parse(isThumbnailEncrypted
|
Uri get thumbnailMxcUrl => Uri.parse(isThumbnailEncrypted
|
||||||
? infoMap['thumbnail_file']['url']
|
? infoMap['thumbnail_file']['url']
|
||||||
: infoMap['thumbnail_url']);
|
: infoMap['thumbnail_url']);
|
||||||
|
|
||||||
/// Gets the mxc url of an attachemnt/thumbnail of a file event, taking sizes into account, or null if not present
|
/// Gets the mxc url of an attachment/thumbnail of a file event, taking sizes into account, or null if not present
|
||||||
Uri attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) {
|
Uri attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) {
|
||||||
if (getThumbnail &&
|
if (getThumbnail &&
|
||||||
infoMap['size'] is int &&
|
infoMap['size'] is int &&
|
||||||
|
|
@ -493,7 +483,7 @@ class Event extends MatrixEvent {
|
||||||
return uint8list != null;
|
return uint8list != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads (and decryptes if necessary) the attachment of this
|
/// Downloads (and decrypts if necessary) the attachment of this
|
||||||
/// event and returns it as a [MatrixFile]. If this event doesn't
|
/// event and returns it as a [MatrixFile]. If this event doesn't
|
||||||
/// contain an attachment, this throws an error. Set [getThumbnail] to
|
/// contain an attachment, this throws an error. Set [getThumbnail] to
|
||||||
/// true to download the thumbnail instead.
|
/// true to download the thumbnail instead.
|
||||||
|
|
@ -671,7 +661,7 @@ class Event extends MatrixEvent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get wether this event has aggregated events from a certain [type]
|
/// Get whether this event has aggregated events from a certain [type]
|
||||||
/// To be able to do that you need to pass a [timeline]
|
/// To be able to do that you need to pass a [timeline]
|
||||||
bool hasAggregatedEvents(Timeline timeline, String type) =>
|
bool hasAggregatedEvents(Timeline timeline, String type) =>
|
||||||
timeline.aggregatedEvents.containsKey(eventId) &&
|
timeline.aggregatedEvents.containsKey(eventId) &&
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/// Defines event status:
|
||||||
|
/// - removed
|
||||||
|
/// - error: (http request failed)
|
||||||
|
/// - sending: (http request started)
|
||||||
|
/// - sent: (http request successful)
|
||||||
|
/// - synced: (event came from sync loop)
|
||||||
|
/// - roomState
|
||||||
|
enum EventStatus {
|
||||||
|
removed,
|
||||||
|
error,
|
||||||
|
sending,
|
||||||
|
sent,
|
||||||
|
synced,
|
||||||
|
roomState,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `EventStatusEnum` value from `intValue`.
|
||||||
|
///
|
||||||
|
/// - -2 == removed;
|
||||||
|
/// - -1 == error;
|
||||||
|
/// - 0 == sending;
|
||||||
|
/// - 1 == sent;
|
||||||
|
/// - 2 == synced;
|
||||||
|
/// - 3 == roomState;
|
||||||
|
EventStatus eventStatusFromInt(int intValue) =>
|
||||||
|
EventStatus.values[intValue + 2];
|
||||||
|
|
||||||
|
/// Takes two [EventStatus] values and returns the one with higher
|
||||||
|
/// (better in terms of message sending) status.
|
||||||
|
EventStatus latestEventStatus(EventStatus status1, EventStatus status2) =>
|
||||||
|
status1.intValue > status2.intValue ? status1 : status2;
|
||||||
|
|
||||||
|
extension EventStatusExtension on EventStatus {
|
||||||
|
/// Returns int value of the event status.
|
||||||
|
///
|
||||||
|
/// - -2 == removed;
|
||||||
|
/// - -1 == error;
|
||||||
|
/// - 0 == sending;
|
||||||
|
/// - 1 == sent;
|
||||||
|
/// - 2 == synced;
|
||||||
|
/// - 3 == roomState;
|
||||||
|
int get intValue => (index - 2);
|
||||||
|
|
||||||
|
/// Return `true` if the `EventStatus` equals `removed`.
|
||||||
|
bool get isRemoved => this == EventStatus.removed;
|
||||||
|
|
||||||
|
/// Return `true` if the `EventStatus` equals `error`.
|
||||||
|
bool get isError => this == EventStatus.error;
|
||||||
|
|
||||||
|
/// Return `true` if the `EventStatus` equals `sending`.
|
||||||
|
bool get isSending => this == EventStatus.sending;
|
||||||
|
|
||||||
|
/// Return `true` if the `EventStatus` equals `roomState`.
|
||||||
|
bool get isRoomState => this == EventStatus.roomState;
|
||||||
|
|
||||||
|
/// Returns `true` if the status is sent or later:
|
||||||
|
/// [EventStatus.sent], [EventStatus.synced] or [EventStatus.roomState].
|
||||||
|
bool get isSent => [
|
||||||
|
EventStatus.sent,
|
||||||
|
EventStatus.synced,
|
||||||
|
EventStatus.roomState
|
||||||
|
].contains(this);
|
||||||
|
|
||||||
|
/// Returns `true` if the status is `synced` or `roomState`:
|
||||||
|
/// [EventStatus.synced] or [EventStatus.roomState].
|
||||||
|
bool get isSynced => [
|
||||||
|
EventStatus.synced,
|
||||||
|
EventStatus.roomState,
|
||||||
|
].contains(this);
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import 'package:matrix/src/utils/space_child.dart';
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
import 'client.dart';
|
import 'client.dart';
|
||||||
import 'event.dart';
|
import 'event.dart';
|
||||||
|
import 'event_status.dart';
|
||||||
import 'timeline.dart';
|
import 'timeline.dart';
|
||||||
import 'user.dart';
|
import 'user.dart';
|
||||||
import 'voip_content.dart';
|
import 'voip_content.dart';
|
||||||
|
|
@ -819,7 +820,7 @@ class Room {
|
||||||
senderId: client.userID,
|
senderId: client.userID,
|
||||||
originServerTs: sentDate,
|
originServerTs: sentDate,
|
||||||
unsigned: {
|
unsigned: {
|
||||||
messageSendingStatusKey: 0,
|
messageSendingStatusKey: EventStatus.sending.intValue,
|
||||||
'transaction_id': messageID,
|
'transaction_id': messageID,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -846,14 +847,14 @@ class Room {
|
||||||
} else {
|
} else {
|
||||||
Logs().w('[Client] Problem while sending message', e, s);
|
Logs().w('[Client] Problem while sending message', e, s);
|
||||||
syncUpdate.rooms.join.values.first.timeline.events.first
|
syncUpdate.rooms.join.values.first.timeline.events.first
|
||||||
.unsigned[messageSendingStatusKey] = -1;
|
.unsigned[messageSendingStatusKey] = EventStatus.error.intValue;
|
||||||
await _handleFakeSync(syncUpdate);
|
await _handleFakeSync(syncUpdate);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syncUpdate.rooms.join.values.first.timeline.events.first
|
syncUpdate.rooms.join.values.first.timeline.events.first
|
||||||
.unsigned[messageSendingStatusKey] = 1;
|
.unsigned[messageSendingStatusKey] = EventStatus.sent.intValue;
|
||||||
syncUpdate.rooms.join.values.first.timeline.events.first.eventId = res;
|
syncUpdate.rooms.join.values.first.timeline.events.first.eventId = res;
|
||||||
await _handleFakeSync(syncUpdate);
|
await _handleFakeSync(syncUpdate);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import '../matrix.dart';
|
import '../matrix.dart';
|
||||||
import 'event.dart';
|
import 'event.dart';
|
||||||
|
import 'event_status.dart';
|
||||||
import 'room.dart';
|
import 'room.dart';
|
||||||
import 'utils/event_update.dart';
|
import 'utils/event_update.dart';
|
||||||
|
|
||||||
|
|
@ -243,11 +244,11 @@ class Timeline {
|
||||||
eventUpdate.type != EventUpdateType.history) {
|
eventUpdate.type != EventUpdateType.history) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final status = eventUpdate.content['status'] ??
|
final status = eventStatusFromInt(eventUpdate.content['status'] ??
|
||||||
(eventUpdate.content['unsigned'] is Map<String, dynamic>
|
(eventUpdate.content['unsigned'] is Map<String, dynamic>
|
||||||
? eventUpdate.content['unsigned'][messageSendingStatusKey]
|
? eventUpdate.content['unsigned'][messageSendingStatusKey]
|
||||||
: null) ??
|
: null) ??
|
||||||
2;
|
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 eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
||||||
|
|
@ -258,7 +259,7 @@ class Timeline {
|
||||||
room,
|
room,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if (status == -2) {
|
} 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]);
|
||||||
|
|
@ -279,7 +280,8 @@ class Timeline {
|
||||||
room,
|
room,
|
||||||
);
|
);
|
||||||
// do we preserve the status? we should allow 0 -> -1 updates and status increases
|
// do we preserve the status? we should allow 0 -> -1 updates and status increases
|
||||||
if (status < oldStatus && !(status == -1 && oldStatus == 0)) {
|
if ((latestEventStatus(status, oldStatus) == oldStatus) &&
|
||||||
|
!(status.isError && oldStatus.isSending)) {
|
||||||
events[i].status = oldStatus;
|
events[i].status = oldStatus;
|
||||||
}
|
}
|
||||||
addAggregatedEvent(events[i]);
|
addAggregatedEvent(events[i]);
|
||||||
|
|
@ -315,7 +317,7 @@ class Timeline {
|
||||||
extension on List<Event> {
|
extension on List<Event> {
|
||||||
int get firstIndexWhereNotError {
|
int get firstIndexWhereNotError {
|
||||||
if (isEmpty) return 0;
|
if (isEmpty) return 0;
|
||||||
final index = indexWhere((e) => e.status != -1);
|
final index = indexWhere((event) => !event.status.isError);
|
||||||
if (index == -1) return length;
|
if (index == -1) return length;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
/*
|
||||||
|
* Famedly Matrix SDK
|
||||||
|
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import '../matrix.dart';
|
||||||
|
import 'event.dart';
|
||||||
|
import 'event_status.dart';
|
||||||
|
import 'room.dart';
|
||||||
|
import 'utils/event_update.dart';
|
||||||
|
|
||||||
|
/// Represents the timeline of a room. The callback [onUpdate] will be triggered
|
||||||
|
/// automatically. The initial
|
||||||
|
/// event list will be retreived when created by the `room.getTimeline()` method.
|
||||||
|
class Timeline {
|
||||||
|
final Room room;
|
||||||
|
final List<Event> events;
|
||||||
|
|
||||||
|
/// Map of event ID to map of type to set of aggregated events
|
||||||
|
final Map<String, Map<String, Set<Event>>> aggregatedEvents = {};
|
||||||
|
|
||||||
|
final void Function()? onUpdate;
|
||||||
|
final void Function(int insertID)? onInsert;
|
||||||
|
|
||||||
|
StreamSubscription<EventUpdate>? sub;
|
||||||
|
StreamSubscription<SyncUpdate>? roomSub;
|
||||||
|
StreamSubscription<String>? sessionIdReceivedSub;
|
||||||
|
bool isRequestingHistory = false;
|
||||||
|
|
||||||
|
final 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 (final event in events) {
|
||||||
|
if (event.eventId == id) return event;
|
||||||
|
}
|
||||||
|
if (_eventCache.containsKey(id)) return _eventCache[id];
|
||||||
|
final requestedEvent = await room.getEventById(id);
|
||||||
|
if (requestedEvent == null) return null;
|
||||||
|
_eventCache[id] = requestedEvent;
|
||||||
|
return _eventCache[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// When fetching history, we will collect them into the `_historyUpdates` set
|
||||||
|
// first, and then only process all events at once, once we have the full history.
|
||||||
|
// This ensures that the entire history fetching only triggers `onUpdate` only *once*,
|
||||||
|
// even if /sync's complete while history is being proccessed.
|
||||||
|
bool _collectHistoryUpdates = false;
|
||||||
|
|
||||||
|
bool get canRequestHistory {
|
||||||
|
if (events.isEmpty) return true;
|
||||||
|
return events.last.type != EventTypes.RoomCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> requestHistory(
|
||||||
|
{int historyCount = Room.defaultHistoryCount}) async {
|
||||||
|
if (isRequestingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isRequestingHistory = true;
|
||||||
|
onUpdate?.call();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Look up for events in hive first
|
||||||
|
final eventsFromStore = await room.client.database?.getEventList(
|
||||||
|
room,
|
||||||
|
start: events.length,
|
||||||
|
limit: Room.defaultHistoryCount,
|
||||||
|
);
|
||||||
|
if (eventsFromStore != null && eventsFromStore.isNotEmpty) {
|
||||||
|
events.addAll(eventsFromStore);
|
||||||
|
} else {
|
||||||
|
Logs().v('No more events found in the store. Request from server...');
|
||||||
|
await room.requestHistory(
|
||||||
|
historyCount: historyCount,
|
||||||
|
onHistoryReceived: () {
|
||||||
|
_collectHistoryUpdates = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_collectHistoryUpdates = false;
|
||||||
|
isRequestingHistory = false;
|
||||||
|
onUpdate?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timeline(
|
||||||
|
{required this.room, List<Event>? events, this.onUpdate, this.onInsert})
|
||||||
|
: 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
|
||||||
|
.where((sync) => sync.rooms?.join?[room.id]?.timeline?.limited == true)
|
||||||
|
.listen((_) {
|
||||||
|
this.events.clear();
|
||||||
|
aggregatedEvents.clear();
|
||||||
|
});
|
||||||
|
sessionIdReceivedSub =
|
||||||
|
room.onSessionKeyReceived.stream.listen(_sessionKeyReceived);
|
||||||
|
|
||||||
|
// we want to populate our aggregated events
|
||||||
|
for (final e in this.events) {
|
||||||
|
addAggregatedEvent(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Don't forget to call this before you dismiss this object!
|
||||||
|
void cancelSubscriptions() {
|
||||||
|
sub?.cancel();
|
||||||
|
roomSub?.cancel();
|
||||||
|
sessionIdReceivedSub?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sessionKeyReceived(String sessionId) async {
|
||||||
|
var decryptAtLeastOneEvent = false;
|
||||||
|
final decryptFn = () async {
|
||||||
|
if (!room.client.encryptionEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
if (events[i].type == EventTypes.Encrypted &&
|
||||||
|
events[i].messageType == MessageTypes.BadEncrypted &&
|
||||||
|
events[i].content['session_id'] == sessionId) {
|
||||||
|
events[i] = await room.client.encryption
|
||||||
|
.decryptRoomEvent(room.id, events[i], store: true);
|
||||||
|
if (events[i].type != EventTypes.Encrypted) {
|
||||||
|
decryptAtLeastOneEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (room.client.database != null) {
|
||||||
|
await room.client.database.transaction(decryptFn);
|
||||||
|
} else {
|
||||||
|
await decryptFn();
|
||||||
|
}
|
||||||
|
if (decryptAtLeastOneEvent) onUpdate?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request the keys for undecryptable events of this timeline
|
||||||
|
void requestKeys() {
|
||||||
|
for (final event in events) {
|
||||||
|
if (event.type == EventTypes.Encrypted &&
|
||||||
|
event.messageType == MessageTypes.BadEncrypted &&
|
||||||
|
event.content['can_request_session'] == true) {
|
||||||
|
try {
|
||||||
|
room.client.encryption.keyManager.maybeAutoRequest(room.id,
|
||||||
|
event.content['session_id'], event.content['sender_key']);
|
||||||
|
} catch (_) {
|
||||||
|
// dispose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _findEvent({String? event_id, String? unsigned_txid}) {
|
||||||
|
// we want to find any existing event where either the passed event_id or the passed unsigned_txid
|
||||||
|
// matches either the event_id or transaction_id of the existing event.
|
||||||
|
// For that we create two sets, searchNeedle, what we search, and searchHaystack, where we check if there is a match.
|
||||||
|
// Now, after having these two sets, if the intersect between them is non-empty, we know that we have at least one match in one pair,
|
||||||
|
// thus meaning we found our element.
|
||||||
|
final searchNeedle = <String>{};
|
||||||
|
if (event_id != null) {
|
||||||
|
searchNeedle.add(event_id);
|
||||||
|
}
|
||||||
|
if (unsigned_txid != null) {
|
||||||
|
searchNeedle.add(unsigned_txid);
|
||||||
|
}
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < events.length; i++) {
|
||||||
|
final searchHaystack = <String>{};
|
||||||
|
if (events[i].eventId != null) {
|
||||||
|
searchHaystack.add(events[i].eventId);
|
||||||
|
}
|
||||||
|
if (events[i].unsigned != null &&
|
||||||
|
events[i].unsigned['transaction_id'] != null) {
|
||||||
|
searchHaystack.add(events[i].unsigned['transaction_id']);
|
||||||
|
}
|
||||||
|
if (searchNeedle.intersection(searchHaystack).isNotEmpty) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeEventFromSet(Set<Event> eventSet, Event event) {
|
||||||
|
eventSet.removeWhere((e) =>
|
||||||
|
e.matchesEventOrTransactionId(event.eventId) ||
|
||||||
|
(event.unsigned != null &&
|
||||||
|
e.matchesEventOrTransactionId(event.unsigned['transaction_id'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAggregatedEvent(Event event) {
|
||||||
|
// we want to add an event to the aggregation tree
|
||||||
|
if (event.relationshipType == null || event.relationshipEventId == null) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
if (!aggregatedEvents.containsKey(event.relationshipEventId)) {
|
||||||
|
aggregatedEvents[event.relationshipEventId] = <String, Set<Event>>{};
|
||||||
|
}
|
||||||
|
final events = (aggregatedEvents[event.relationshipEventId] ??=
|
||||||
|
<String, Set<Event>>{})[event.relationshipType] ??= <Event>{};
|
||||||
|
// remove a potential old event
|
||||||
|
_removeEventFromSet(events, event);
|
||||||
|
// add the new one
|
||||||
|
events.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAggregatedEvent(Event event) {
|
||||||
|
aggregatedEvents.remove(event.eventId);
|
||||||
|
if (event.unsigned != null) {
|
||||||
|
aggregatedEvents.remove(event.unsigned['transaction_id']);
|
||||||
|
}
|
||||||
|
for (final types in aggregatedEvents.values) {
|
||||||
|
for (final events in types.values) {
|
||||||
|
_removeEventFromSet(events, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleEventUpdate(EventUpdate eventUpdate, {bool update = true}) {
|
||||||
|
try {
|
||||||
|
if (eventUpdate.roomID != room.id) return;
|
||||||
|
|
||||||
|
if (eventUpdate.type != EventUpdateType.timeline &&
|
||||||
|
eventUpdate.type != EventUpdateType.history) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final status = eventStatusFromInt(eventUpdate.content['status'] ??
|
||||||
|
(eventUpdate.content['unsigned'] is Map<String, dynamic>
|
||||||
|
? eventUpdate.content['unsigned'][messageSendingStatusKey]
|
||||||
|
: null) ??
|
||||||
|
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(
|
||||||
|
eventUpdate.content,
|
||||||
|
room,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (status.isRemoved) {
|
||||||
|
final i = _findEvent(event_id: eventUpdate.content['event_id']);
|
||||||
|
if (i < events.length) {
|
||||||
|
removeAggregatedEvent(events[i]);
|
||||||
|
events.removeAt(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final i = _findEvent(
|
||||||
|
event_id: eventUpdate.content['event_id'],
|
||||||
|
unsigned_txid: eventUpdate.content['unsigned'] is Map
|
||||||
|
? eventUpdate.content['unsigned']['transaction_id']
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (i < events.length) {
|
||||||
|
// if the old status is larger than the new one, we also want to preserve the old status
|
||||||
|
final oldStatus = events[i].status;
|
||||||
|
events[i] = Event.fromJson(
|
||||||
|
eventUpdate.content,
|
||||||
|
room,
|
||||||
|
);
|
||||||
|
// do we preserve the status? we should allow 0 -> -1 updates and status increases
|
||||||
|
if ((latestEventStatus(status, oldStatus) == oldStatus) &&
|
||||||
|
!(status.isError && oldStatus.isSending)) {
|
||||||
|
events[i].status = oldStatus;
|
||||||
|
}
|
||||||
|
addAggregatedEvent(events[i]);
|
||||||
|
} else {
|
||||||
|
final newEvent = Event.fromJson(
|
||||||
|
eventUpdate.content,
|
||||||
|
room,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (eventUpdate.type == EventUpdateType.history &&
|
||||||
|
events.indexWhere(
|
||||||
|
(e) => e.eventId == eventUpdate.content['event_id']) !=
|
||||||
|
-1) return;
|
||||||
|
if (eventUpdate.type == EventUpdateType.history) {
|
||||||
|
events.add(newEvent);
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
} else if (status.isError) {
|
||||||
|
events.insert(events.firstIndexWhereNotError, newEvent);
|
||||||
|
>>>>>>> 8fe85aca09b947ce7417672ce57ebfce80f3133c
|
||||||
|
} else {
|
||||||
|
events.insert(events.firstIndexWhereNotError, newEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
addAggregatedEvent(newEvent);
|
||||||
|
onInsert?.call(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update && !_collectHistoryUpdates) {
|
||||||
|
onUpdate?.call();
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().w('Handle event update failed', e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on List<Event> {
|
||||||
|
int get firstIndexWhereNotError {
|
||||||
|
if (isEmpty) return 0;
|
||||||
|
final index = indexWhere((event) => !event.status.isError);
|
||||||
|
if (index == -1) return length;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* Famedly Matrix SDK
|
||||||
|
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import '../matrix.dart';
|
||||||
|
|
||||||
|
import 'event.dart';
|
||||||
|
import 'room.dart';
|
||||||
|
|
||||||
|
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||||
|
class User extends Event {
|
||||||
|
factory User(
|
||||||
|
String id, {
|
||||||
|
String? membership,
|
||||||
|
String? displayName,
|
||||||
|
String? avatarUrl,
|
||||||
|
Room? room,
|
||||||
|
}) {
|
||||||
|
return User.fromState(
|
||||||
|
stateKey: id,
|
||||||
|
content: {
|
||||||
|
if (membership != null) 'membership': membership,
|
||||||
|
if (displayName != null) 'displayname': displayName,
|
||||||
|
if (avatarUrl != null) 'avatar_url': avatarUrl,
|
||||||
|
},
|
||||||
|
typeKey: EventTypes.RoomMember,
|
||||||
|
roomId: room?.id,
|
||||||
|
room: room,
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
User.fromState(
|
||||||
|
{dynamic prevContent,
|
||||||
|
required String stateKey,
|
||||||
|
dynamic content,
|
||||||
|
required String typeKey,
|
||||||
|
String? eventId,
|
||||||
|
String? roomId,
|
||||||
|
String? senderId,
|
||||||
|
required DateTime originServerTs,
|
||||||
|
dynamic unsigned,
|
||||||
|
Room? room})
|
||||||
|
: super(
|
||||||
|
stateKey: stateKey,
|
||||||
|
prevContent: prevContent,
|
||||||
|
content: content,
|
||||||
|
type: typeKey,
|
||||||
|
eventId: eventId,
|
||||||
|
roomId: roomId,
|
||||||
|
senderId: senderId,
|
||||||
|
originServerTs: originServerTs,
|
||||||
|
unsigned: unsigned,
|
||||||
|
room: room);
|
||||||
|
|
||||||
|
/// The full qualified Matrix ID in the format @username:server.abc.
|
||||||
|
String get id => stateKey;
|
||||||
|
|
||||||
|
/// The displayname of the user if the user has set one.
|
||||||
|
<<<<<<< HEAD
|
||||||
|
String? get displayName =>
|
||||||
|
=======
|
||||||
|
String get displayName =>
|
||||||
|
>>>>>>> 8fe85aca09b947ce7417672ce57ebfce80f3133c
|
||||||
|
content?.tryGet<String>('displayname') ??
|
||||||
|
prevContent?.tryGet<String>('displayname');
|
||||||
|
|
||||||
|
/// Returns the power level of this user.
|
||||||
|
int get powerLevel => room?.getPowerLevelByUserId(id) ?? 0;
|
||||||
|
|
||||||
|
/// The membership status of the user. One of:
|
||||||
|
/// join
|
||||||
|
/// invite
|
||||||
|
/// leave
|
||||||
|
/// ban
|
||||||
|
Membership get membership => Membership.values.firstWhere((e) {
|
||||||
|
if (content['membership'] != null) {
|
||||||
|
return e.toString() == 'Membership.' + content['membership'];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, orElse: () => Membership.join);
|
||||||
|
|
||||||
|
/// The avatar if the user has one.
|
||||||
|
Uri? get avatarUrl => content != null && content.containsKey('avatar_url')
|
||||||
|
? (content['avatar_url'] is String
|
||||||
|
? Uri.tryParse(content['avatar_url'])
|
||||||
|
: null)
|
||||||
|
: (prevContent != null && prevContent['avatar_url'] is String
|
||||||
|
? Uri.tryParse(prevContent['avatar_url'])
|
||||||
|
: null);
|
||||||
|
|
||||||
|
/// Returns the displayname or the local part of the Matrix ID if the user
|
||||||
|
/// has no displayname. If [formatLocalpart] is true, then the localpart will
|
||||||
|
/// be formatted in the way, that all "_" characters are becomming white spaces and
|
||||||
|
/// the first character of each word becomes uppercase.
|
||||||
|
/// If [mxidLocalPartFallback] is true, then the local part of the mxid will be shown
|
||||||
|
/// if there is no other displayname available. If not then this will return "Unknown user".
|
||||||
|
String calcDisplayname({
|
||||||
|
bool? formatLocalpart,
|
||||||
|
bool? mxidLocalPartFallback,
|
||||||
|
}) {
|
||||||
|
formatLocalpart ??= room?.client?.formatLocalpart ?? true;
|
||||||
|
mxidLocalPartFallback ??= room?.client?.mxidLocalPartFallback ?? true;
|
||||||
|
final displayName = this.displayName;
|
||||||
|
if (displayName != null && displayName.isNotEmpty) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
final stateKey = this.stateKey;
|
||||||
|
if (stateKey != null && mxidLocalPartFallback) {
|
||||||
|
if (!formatLocalpart) {
|
||||||
|
return stateKey.localpart ?? '';
|
||||||
|
}
|
||||||
|
final words = stateKey.localpart?.replaceAll('_', ' ').split(' ') ?? [];
|
||||||
|
for (var i = 0; i < words.length; i++) {
|
||||||
|
if (words[i].isNotEmpty) {
|
||||||
|
words[i] = words[i][0].toUpperCase() + words[i].substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return words.join(' ');
|
||||||
|
}
|
||||||
|
return 'Unknown user';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the Matrix API to kick this user from this room.
|
||||||
|
Future<void> kick() => room.kick(id);
|
||||||
|
|
||||||
|
/// Call the Matrix API to ban this user from this room.
|
||||||
|
Future<void> ban() => room.ban(id);
|
||||||
|
|
||||||
|
/// Call the Matrix API to unban this banned user from this room.
|
||||||
|
Future<void> unban() => room.unban(id);
|
||||||
|
|
||||||
|
/// Call the Matrix API to change the power level of this user.
|
||||||
|
Future<void> setPower(int power) => room.setPower(id, power);
|
||||||
|
|
||||||
|
/// Returns an existing direct chat ID with this user or creates a new one.
|
||||||
|
/// Returns null on error.
|
||||||
|
Future<String?> startDirectChat() => room.client.startDirectChat(id);
|
||||||
|
|
||||||
|
/// The newest presence of this user if there is any and null if not.
|
||||||
|
Presence? get presence => room.client.presences[id];
|
||||||
|
|
||||||
|
/// Whether the client is able to ban/unban this user.
|
||||||
|
bool get canBan => room.canBan && powerLevel < room.ownPowerLevel;
|
||||||
|
|
||||||
|
/// Whether the client is able to kick this user.
|
||||||
|
bool get canKick =>
|
||||||
|
[Membership.join, Membership.invite].contains(membership) &&
|
||||||
|
room.canKick &&
|
||||||
|
powerLevel < room.ownPowerLevel;
|
||||||
|
|
||||||
|
/// Whether the client is allowed to change the power level of this user.
|
||||||
|
/// Please be aware that you can only set the power level to at least your own!
|
||||||
|
bool get canChangePowerLevel =>
|
||||||
|
room.canChangePowerLevel && powerLevel < room.ownPowerLevel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(dynamic other) => (other is User &&
|
||||||
|
other.id == id &&
|
||||||
|
other.room == room &&
|
||||||
|
other.membership == membership);
|
||||||
|
|
||||||
|
/// Get the mention text to use in a plain text body to mention this specific user
|
||||||
|
/// in this specific room
|
||||||
|
String get mention {
|
||||||
|
// if the displayname has [ or ] or : we can't build our more fancy stuff, so fall back to the id
|
||||||
|
// [] is used for the delimitors
|
||||||
|
// If we allowed : we could get collissions with the mxid fallbacks
|
||||||
|
final displayName = this.displayName;
|
||||||
|
if (displayName == null ||
|
||||||
|
displayName.isEmpty ||
|
||||||
|
{'[', ']', ':'}.any(displayName.contains)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
final identifier = '@' +
|
||||||
|
// if we have non-word characters we need to surround with []
|
||||||
|
(RegExp(r'^\w+$').hasMatch(displayName)
|
||||||
|
? displayName
|
||||||
|
: '[$displayName]');
|
||||||
|
|
||||||
|
// get all the users with the same display name
|
||||||
|
final allUsersWithSameDisplayname = room.getParticipants();
|
||||||
|
allUsersWithSameDisplayname.removeWhere((user) =>
|
||||||
|
user.id == id ||
|
||||||
|
(user.displayName?.isEmpty ?? true) ||
|
||||||
|
user.displayName != displayName);
|
||||||
|
if (allUsersWithSameDisplayname.isEmpty) {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
// ok, we have multiple users with the same display name....time to calculate a hash
|
||||||
|
final hashes = allUsersWithSameDisplayname.map((u) => _hash(u.id));
|
||||||
|
final ourHash = _hash(id);
|
||||||
|
// hash collission...just return our own mxid again
|
||||||
|
if (hashes.contains(ourHash)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
return '$identifier#$ourHash';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the mention fragments for this user.
|
||||||
|
Set<String> get mentionFragments {
|
||||||
|
final displayName = this.displayName;
|
||||||
|
if (displayName == null ||
|
||||||
|
displayName.isEmpty ||
|
||||||
|
{'[', ']', ':'}.any(displayName.contains)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
final identifier = '@' +
|
||||||
|
// if we have non-word characters we need to surround with []
|
||||||
|
(RegExp(r'^\w+$').hasMatch(displayName)
|
||||||
|
? displayName
|
||||||
|
: '[$displayName]');
|
||||||
|
|
||||||
|
final hash = _hash(id);
|
||||||
|
return {identifier, '$identifier#$hash'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
const _maximumHashLength = 10000;
|
||||||
|
String _hash(String s) =>
|
||||||
|
(s.codeUnits.fold<int>(0, (a, b) => a + b) % _maximumHashLength).toString();
|
||||||
|
=======
|
||||||
|
String _hash(String s) =>
|
||||||
|
(s.codeUnits.fold<int>(0, (a, b) => a + b) % 10000).toString();
|
||||||
|
>>>>>>> 8fe85aca09b947ce7417672ce57ebfce80f3133c
|
||||||
|
|
@ -23,6 +23,7 @@ import 'dart:typed_data';
|
||||||
import 'package:matrix/encryption.dart';
|
import 'package:matrix/encryption.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix/src/event.dart';
|
import 'package:matrix/src/event.dart';
|
||||||
|
import 'package:matrix/src/event_status.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ void main() {
|
||||||
'origin_server_ts': timestamp,
|
'origin_server_ts': timestamp,
|
||||||
'type': type,
|
'type': type,
|
||||||
'room_id': '1234',
|
'room_id': '1234',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'content': contentJson,
|
'content': contentJson,
|
||||||
};
|
};
|
||||||
final client = Client('testclient', httpClient: FakeMatrixApi());
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
|
|
@ -79,7 +80,7 @@ void main() {
|
||||||
|
|
||||||
expect(event.eventId, id);
|
expect(event.eventId, id);
|
||||||
expect(event.senderId, senderID);
|
expect(event.senderId, senderID);
|
||||||
expect(event.status, 2);
|
expect(event.status, EventStatus.synced);
|
||||||
expect(event.text, body);
|
expect(event.text, body);
|
||||||
expect(event.formattedText, formatted_body);
|
expect(event.formattedText, formatted_body);
|
||||||
expect(event.body, body);
|
expect(event.body, body);
|
||||||
|
|
@ -89,7 +90,7 @@ void main() {
|
||||||
final state = Event.fromJson(jsonObj, null);
|
final state = Event.fromJson(jsonObj, null);
|
||||||
expect(state.eventId, id);
|
expect(state.eventId, id);
|
||||||
expect(state.stateKey, '');
|
expect(state.stateKey, '');
|
||||||
expect(state.status, 2);
|
expect(state.status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
test('Test all EventTypes', () async {
|
test('Test all EventTypes', () async {
|
||||||
Event event;
|
Event event;
|
||||||
|
|
@ -259,7 +260,7 @@ void main() {
|
||||||
final event = Event.fromJson(
|
final event = Event.fromJson(
|
||||||
jsonObj, Room(id: '1234', client: Client('testclient')));
|
jsonObj, Room(id: '1234', client: Client('testclient')));
|
||||||
final removed1 = await event.remove();
|
final removed1 = await event.remove();
|
||||||
event.status = 0;
|
event.status = EventStatus.sending;
|
||||||
final removed2 = await event.remove();
|
final removed2 = await event.remove();
|
||||||
expect(removed1, false);
|
expect(removed1, false);
|
||||||
expect(removed2, true);
|
expect(removed2, true);
|
||||||
|
|
@ -276,7 +277,7 @@ void main() {
|
||||||
final event = Event.fromJson(
|
final event = Event.fromJson(
|
||||||
jsonObj, Room(id: '!1234:example.com', client: matrix));
|
jsonObj, Room(id: '!1234:example.com', client: matrix));
|
||||||
final resp1 = await event.sendAgain();
|
final resp1 = await event.sendAgain();
|
||||||
event.status = -1;
|
event.status = EventStatus.error;
|
||||||
final resp2 = await event.sendAgain(txid: '1234');
|
final resp2 = await event.sendAgain(txid: '1234');
|
||||||
expect(resp1, null);
|
expect(resp1, null);
|
||||||
expect(resp2.startsWith('\$event'), true);
|
expect(resp2.startsWith('\$event'), true);
|
||||||
|
|
@ -308,7 +309,7 @@ void main() {
|
||||||
'origin_server_ts': timestamp,
|
'origin_server_ts': timestamp,
|
||||||
'type': 'm.room.encrypted',
|
'type': 'm.room.encrypted',
|
||||||
'room_id': '1234',
|
'room_id': '1234',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'content': json.encode({
|
'content': json.encode({
|
||||||
'msgtype': 'm.bad.encrypted',
|
'msgtype': 'm.bad.encrypted',
|
||||||
'body': DecryptException.unknownSession,
|
'body': DecryptException.unknownSession,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:matrix/src/event_status.dart';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'fake_database.dart';
|
import 'fake_database.dart';
|
||||||
|
|
@ -69,7 +70,7 @@ void main() {
|
||||||
'content': <String, dynamic>{'blah': 'blubb'},
|
'content': <String, dynamic>{'blah': 'blubb'},
|
||||||
'event_id': 'transaction-1',
|
'event_id': 'transaction-1',
|
||||||
'sender': '@blah:blubb',
|
'sender': '@blah:blubb',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
|
|
@ -87,7 +88,7 @@ void main() {
|
||||||
'unsigned': <String, dynamic>{
|
'unsigned': <String, dynamic>{
|
||||||
'transaction_id': 'transaction-1',
|
'transaction_id': 'transaction-1',
|
||||||
},
|
},
|
||||||
'status': 1,
|
'status': EventStatus.sent.intValue,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
|
|
@ -105,7 +106,7 @@ void main() {
|
||||||
'content': {'blah': 'blubb'},
|
'content': {'blah': 'blubb'},
|
||||||
'event_id': '\$event-3',
|
'event_id': '\$event-3',
|
||||||
'sender': '@blah:blubb',
|
'sender': '@blah:blubb',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
|
|
@ -120,7 +121,7 @@ void main() {
|
||||||
'content': {'blah': 'blubb'},
|
'content': {'blah': 'blubb'},
|
||||||
'event_id': '\$event-3',
|
'event_id': '\$event-3',
|
||||||
'sender': '@blah:blubb',
|
'sender': '@blah:blubb',
|
||||||
'status': 1,
|
'status': EventStatus.sent.intValue,
|
||||||
'unsigned': <String, dynamic>{
|
'unsigned': <String, dynamic>{
|
||||||
'transaction_id': 'transaction-2',
|
'transaction_id': 'transaction-2',
|
||||||
},
|
},
|
||||||
|
|
@ -129,7 +130,7 @@ void main() {
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
event = await database.getEventById('\$event-3', room);
|
event = await database.getEventById('\$event-3', room);
|
||||||
expect(event.eventId, '\$event-3');
|
expect(event.eventId, '\$event-3');
|
||||||
expect(event.status, 1);
|
expect(event.status, EventStatus.sent);
|
||||||
event = await database.getEventById('transaction-2', room);
|
event = await database.getEventById('transaction-2', room);
|
||||||
expect(event, null);
|
expect(event, null);
|
||||||
|
|
||||||
|
|
@ -143,7 +144,7 @@ void main() {
|
||||||
'content': {'blah': 'blubb'},
|
'content': {'blah': 'blubb'},
|
||||||
'event_id': '\$event-4',
|
'event_id': '\$event-4',
|
||||||
'sender': '@blah:blubb',
|
'sender': '@blah:blubb',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
|
|
@ -158,7 +159,7 @@ void main() {
|
||||||
'content': {'blah': 'blubb'},
|
'content': {'blah': 'blubb'},
|
||||||
'event_id': '\$event-4',
|
'event_id': '\$event-4',
|
||||||
'sender': '@blah:blubb',
|
'sender': '@blah:blubb',
|
||||||
'status': 1,
|
'status': EventStatus.sent.intValue,
|
||||||
'unsigned': <String, dynamic>{
|
'unsigned': <String, dynamic>{
|
||||||
'transaction_id': 'transaction-3',
|
'transaction_id': 'transaction-3',
|
||||||
},
|
},
|
||||||
|
|
@ -167,7 +168,7 @@ void main() {
|
||||||
await database.storeEventUpdate(update);
|
await database.storeEventUpdate(update);
|
||||||
event = await database.getEventById('\$event-4', room);
|
event = await database.getEventById('\$event-4', room);
|
||||||
expect(event.eventId, '\$event-4');
|
expect(event.eventId, '\$event-4');
|
||||||
expect(event.status, 2);
|
expect(event.status, EventStatus.synced);
|
||||||
event = await database.getEventById('transaction-3', room);
|
event = await database.getEventById('transaction-3', room);
|
||||||
expect(event, null);
|
expect(event, null);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:matrix/src/event_status.dart';
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import 'package:matrix/src/client.dart';
|
import 'package:matrix/src/client.dart';
|
||||||
|
|
@ -75,7 +76,7 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': '2',
|
'event_id': '2',
|
||||||
'origin_server_ts': testTimeStamp - 1000
|
'origin_server_ts': testTimeStamp - 1000
|
||||||
},
|
},
|
||||||
|
|
@ -87,7 +88,7 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': '1',
|
'event_id': '1',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
|
|
@ -160,7 +161,7 @@ void main() {
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
final eventId = timeline.events[0].eventId;
|
final eventId = timeline.events[0].eventId;
|
||||||
expect(eventId.startsWith('\$event'), true);
|
expect(eventId.startsWith('\$event'), true);
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -169,7 +170,7 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'test'},
|
'content': {'msgtype': 'm.text', 'body': 'test'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': eventId,
|
'event_id': eventId,
|
||||||
'unsigned': {'transaction_id': '1234'},
|
'unsigned': {'transaction_id': '1234'},
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch
|
||||||
|
|
@ -182,7 +183,7 @@ void main() {
|
||||||
expect(insertList, [0, 0, 0]);
|
expect(insertList, [0, 0, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].eventId, eventId);
|
expect(timeline.events[0].eventId, eventId);
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Send message with error', () async {
|
test('Send message with error', () async {
|
||||||
|
|
@ -193,7 +194,7 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
'event_id': 'abc',
|
'event_id': 'abc',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
|
|
@ -213,9 +214,9 @@ void main() {
|
||||||
expect(updateCount, 13);
|
expect(updateCount, 13);
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 0]);
|
expect(insertList, [0, 0, 0, 0, 0, 0, 0]);
|
||||||
expect(insertList.length, timeline.events.length);
|
expect(insertList.length, timeline.events.length);
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events[1].status, -1);
|
expect(timeline.events[1].status, EventStatus.error);
|
||||||
expect(timeline.events[2].status, -1);
|
expect(timeline.events[2].status, EventStatus.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove message', () async {
|
test('Remove message', () async {
|
||||||
|
|
@ -227,7 +228,7 @@ void main() {
|
||||||
|
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 0]);
|
expect(insertList, [0, 0, 0, 0, 0, 0, 0]);
|
||||||
expect(timeline.events.length, 6);
|
expect(timeline.events.length, 6);
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getEventById', () async {
|
test('getEventById', () async {
|
||||||
|
|
@ -256,14 +257,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': -1,
|
'status': EventStatus.error.intValue,
|
||||||
'event_id': 'new-test-event',
|
'event_id': 'new-test-event',
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'newresend'},
|
'unsigned': {'transaction_id': 'newresend'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
await timeline.events[0].sendAgain();
|
await timeline.events[0].sendAgain();
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
|
@ -272,7 +273,7 @@ void main() {
|
||||||
|
|
||||||
expect(insertList, [0, 0, 0, 0, 0, 0, 0, 0]);
|
expect(insertList, [0, 0, 0, 0, 0, 0, 0, 0]);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Request history', () async {
|
test('Request history', () async {
|
||||||
|
|
@ -317,7 +318,7 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': -1,
|
'status': EventStatus.error.intValue,
|
||||||
'event_id': 'abc',
|
'event_id': 'abc',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
|
|
@ -329,14 +330,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': 'def',
|
'event_id': 'def',
|
||||||
'origin_server_ts': testTimeStamp + 5
|
'origin_server_ts': testTimeStamp + 5
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events[1].status, 2);
|
expect(timeline.events[1].status, EventStatus.synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sending event to failed update', () async {
|
test('sending event to failed update', () async {
|
||||||
|
|
@ -348,13 +349,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
'event_id': 'will-fail',
|
'event_id': 'will-fail',
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -363,13 +364,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': -1,
|
'status': EventStatus.error.intValue,
|
||||||
'event_id': 'will-fail',
|
'event_id': 'will-fail',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
test('sending an event and the http request finishes first, 0 -> 1 -> 2',
|
test('sending an event and the http request finishes first, 0 -> 1 -> 2',
|
||||||
|
|
@ -382,13 +383,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
'event_id': 'transaction',
|
'event_id': 'transaction',
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -397,14 +398,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 1,
|
'status': EventStatus.sent.intValue,
|
||||||
'event_id': '\$event',
|
'event_id': '\$event',
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'}
|
'unsigned': {'transaction_id': 'transaction'}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, EventStatus.sent);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -413,14 +414,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': '\$event',
|
'event_id': '\$event',
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'}
|
'unsigned': {'transaction_id': 'transaction'}
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
test('sending an event where the sync reply arrives first, 0 -> 2 -> 1',
|
test('sending an event where the sync reply arrives first, 0 -> 2 -> 1',
|
||||||
|
|
@ -436,13 +437,13 @@ void main() {
|
||||||
'event_id': 'transaction',
|
'event_id': 'transaction',
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
'unsigned': {
|
'unsigned': {
|
||||||
messageSendingStatusKey: 0,
|
messageSendingStatusKey: EventStatus.sending.intValue,
|
||||||
'transaction_id': 'transaction',
|
'transaction_id': 'transaction',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -455,12 +456,12 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {
|
'unsigned': {
|
||||||
'transaction_id': 'transaction',
|
'transaction_id': 'transaction',
|
||||||
messageSendingStatusKey: 2,
|
messageSendingStatusKey: EventStatus.synced.intValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -473,12 +474,12 @@ void main() {
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {
|
'unsigned': {
|
||||||
'transaction_id': 'transaction',
|
'transaction_id': 'transaction',
|
||||||
messageSendingStatusKey: 1,
|
messageSendingStatusKey: EventStatus.sent.intValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
test('sending an event 0 -> -1 -> 2', () async {
|
test('sending an event 0 -> -1 -> 2', () async {
|
||||||
|
|
@ -490,13 +491,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
'event_id': 'transaction',
|
'event_id': 'transaction',
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -505,13 +506,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': -1,
|
'status': EventStatus.error.intValue,
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, EventStatus.error);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -520,14 +521,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': '\$event',
|
'event_id': '\$event',
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
test('sending an event 0 -> 2 -> -1', () async {
|
test('sending an event 0 -> 2 -> -1', () async {
|
||||||
|
|
@ -539,13 +540,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 0,
|
'status': EventStatus.sending.intValue,
|
||||||
'event_id': 'transaction',
|
'event_id': 'transaction',
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, EventStatus.sending);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -554,14 +555,14 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': 2,
|
'status': EventStatus.synced.intValue,
|
||||||
'event_id': '\$event',
|
'event_id': '\$event',
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: EventUpdateType.timeline,
|
type: EventUpdateType.timeline,
|
||||||
|
|
@ -570,13 +571,13 @@ void main() {
|
||||||
'type': 'm.room.message',
|
'type': 'm.room.message',
|
||||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||||
'sender': '@alice:example.com',
|
'sender': '@alice:example.com',
|
||||||
'status': -1,
|
'status': EventStatus.error.intValue,
|
||||||
'origin_server_ts': testTimeStamp,
|
'origin_server_ts': testTimeStamp,
|
||||||
'unsigned': {'transaction_id': 'transaction'},
|
'unsigned': {'transaction_id': 'transaction'},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, EventStatus.synced);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
});
|
});
|
||||||
test('logout', () async {
|
test('logout', () async {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue