refactor: make event nullsafe
This commit is contained in:
parent
d2ee73f96f
commit
17fd1f22b3
|
|
@ -308,7 +308,7 @@ class Encryption {
|
||||||
await client.database?.storeEventUpdate(
|
await client.database?.storeEventUpdate(
|
||||||
EventUpdate(
|
EventUpdate(
|
||||||
content: event.toJson(),
|
content: event.toJson(),
|
||||||
roomID: event.roomId,
|
roomID: roomId,
|
||||||
type: updateType,
|
type: updateType,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// @dart=2.9
|
|
||||||
/*
|
/*
|
||||||
* Famedly Matrix SDK
|
* Famedly Matrix SDK
|
||||||
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
* Copyright (C) 2019, 2020, 2021 Famedly GmbH
|
||||||
|
|
@ -39,7 +38,13 @@ abstract class RelationshipTypes {
|
||||||
|
|
||||||
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
|
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
|
||||||
class Event extends MatrixEvent {
|
class Event extends MatrixEvent {
|
||||||
User get sender => room.getUserByMXIDSync(senderId ?? '@unknown:unknown');
|
User get sender =>
|
||||||
|
room?.getUserByMXIDSync(senderId) ??
|
||||||
|
User.fromState(
|
||||||
|
stateKey: senderId,
|
||||||
|
typeKey: EventTypes.RoomMember,
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
@Deprecated('Use [originServerTs] instead')
|
@Deprecated('Use [originServerTs] instead')
|
||||||
DateTime get time => originServerTs;
|
DateTime get time => originServerTs;
|
||||||
|
|
@ -48,10 +53,10 @@ class Event extends MatrixEvent {
|
||||||
String get typeKey => type;
|
String get typeKey => type;
|
||||||
|
|
||||||
@Deprecated('Use [sender.calcDisplayname()] instead')
|
@Deprecated('Use [sender.calcDisplayname()] instead')
|
||||||
String get senderName => sender.calcDisplayname();
|
String? get senderName => sender.calcDisplayname();
|
||||||
|
|
||||||
/// The room this event belongs to. May be null.
|
/// The room this event belongs to. May be null.
|
||||||
final Room room;
|
final Room? room;
|
||||||
|
|
||||||
/// The status of this event.
|
/// The status of this event.
|
||||||
EventStatus status;
|
EventStatus status;
|
||||||
|
|
@ -59,33 +64,39 @@ class Event extends MatrixEvent {
|
||||||
static const EventStatus defaultStatus = EventStatus.synced;
|
static const EventStatus defaultStatus = EventStatus.synced;
|
||||||
|
|
||||||
/// 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 {
|
||||||
unsigned != null && unsigned['redacted_because'] is Map
|
final redacted_because = unsigned?['redacted_because'];
|
||||||
? Event.fromJson(unsigned['redacted_because'], room)
|
final room = this.room;
|
||||||
: null;
|
return (redacted_because is Map<String, dynamic>)
|
||||||
|
? Event.fromJson(redacted_because, room)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
bool get redacted => redactedBecause != null;
|
bool get redacted => redactedBecause != null;
|
||||||
|
|
||||||
User get stateKeyUser => room.getUserByMXIDSync(stateKey);
|
User? get stateKeyUser => room?.getUserByMXIDSync(stateKey);
|
||||||
|
|
||||||
Event({
|
Event({
|
||||||
this.status = defaultStatus,
|
this.status = defaultStatus,
|
||||||
Map<String, dynamic> content,
|
required Map<String, dynamic> content,
|
||||||
String type,
|
required String type,
|
||||||
String eventId,
|
required String eventId,
|
||||||
String roomId,
|
String? roomId,
|
||||||
String senderId,
|
required String senderId,
|
||||||
DateTime originServerTs,
|
required DateTime originServerTs,
|
||||||
Map<String, dynamic> unsigned,
|
Map<String, dynamic>? unsigned,
|
||||||
Map<String, dynamic> prevContent,
|
Map<String, dynamic>? prevContent,
|
||||||
String stateKey,
|
String? stateKey,
|
||||||
this.room,
|
this.room,
|
||||||
}) {
|
}) : super(
|
||||||
this.content = content;
|
content: content,
|
||||||
this.type = type;
|
type: type,
|
||||||
|
eventId: eventId,
|
||||||
|
senderId: senderId,
|
||||||
|
originServerTs: originServerTs,
|
||||||
|
roomId: roomId ?? room?.id,
|
||||||
|
) {
|
||||||
this.eventId = eventId;
|
this.eventId = eventId;
|
||||||
this.roomId = roomId ?? room?.id;
|
|
||||||
this.senderId = senderId;
|
|
||||||
this.unsigned = unsigned;
|
this.unsigned = unsigned;
|
||||||
// synapse unfortunately 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.
|
||||||
|
|
@ -103,7 +114,6 @@ class Event extends MatrixEvent {
|
||||||
// A strange bug in dart web makes this crash
|
// A strange bug in dart web makes this crash
|
||||||
}
|
}
|
||||||
this.stateKey = stateKey;
|
this.stateKey = stateKey;
|
||||||
this.originServerTs = originServerTs;
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -113,7 +123,8 @@ class Event extends MatrixEvent {
|
||||||
final age = DateTime.now().millisecondsSinceEpoch -
|
final age = DateTime.now().millisecondsSinceEpoch -
|
||||||
originServerTs.millisecondsSinceEpoch;
|
originServerTs.millisecondsSinceEpoch;
|
||||||
|
|
||||||
if (age > room.client.sendMessageTimeoutSeconds * 1000) {
|
final room = this.room;
|
||||||
|
if (room != null && age > room.client.sendMessageTimeoutSeconds * 1000) {
|
||||||
// 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>{};
|
||||||
|
|
@ -143,7 +154,7 @@ class Event extends MatrixEvent {
|
||||||
factory Event.fromMatrixEvent(
|
factory Event.fromMatrixEvent(
|
||||||
MatrixEvent matrixEvent,
|
MatrixEvent matrixEvent,
|
||||||
Room room, {
|
Room room, {
|
||||||
EventStatus status,
|
EventStatus status = defaultStatus,
|
||||||
}) =>
|
}) =>
|
||||||
Event(
|
Event(
|
||||||
status: status,
|
status: status,
|
||||||
|
|
@ -162,7 +173,7 @@ class Event extends MatrixEvent {
|
||||||
/// Get a State event from a table row or from the event stream.
|
/// Get a State event from a table row or from the event stream.
|
||||||
factory Event.fromJson(
|
factory Event.fromJson(
|
||||||
Map<String, dynamic> jsonPayload,
|
Map<String, dynamic> jsonPayload,
|
||||||
Room room,
|
Room? room,
|
||||||
) {
|
) {
|
||||||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||||
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
|
|
@ -190,7 +201,7 @@ class Event extends MatrixEvent {
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final data = <String, dynamic>{};
|
final data = <String, dynamic>{};
|
||||||
if (stateKey != null) data['state_key'] = stateKey;
|
if (stateKey != null) data['state_key'] = stateKey;
|
||||||
if (prevContent != null && prevContent.isNotEmpty) {
|
if (prevContent?.isNotEmpty == true) {
|
||||||
data['prev_content'] = prevContent;
|
data['prev_content'] = prevContent;
|
||||||
}
|
}
|
||||||
data['content'] = content;
|
data['content'] = content;
|
||||||
|
|
@ -199,14 +210,15 @@ class Event extends MatrixEvent {
|
||||||
data['room_id'] = roomId;
|
data['room_id'] = roomId;
|
||||||
data['sender'] = senderId;
|
data['sender'] = senderId;
|
||||||
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
|
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
|
||||||
if (unsigned != null && unsigned.isNotEmpty) {
|
if (unsigned?.isNotEmpty == true) {
|
||||||
data['unsigned'] = unsigned;
|
data['unsigned'] = unsigned;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
User get asUser => User.fromState(
|
User get asUser => User.fromState(
|
||||||
stateKey: stateKey,
|
// state key should always be set for member events
|
||||||
|
stateKey: stateKey!,
|
||||||
prevContent: prevContent,
|
prevContent: prevContent,
|
||||||
content: content,
|
content: content,
|
||||||
typeKey: type,
|
typeKey: type,
|
||||||
|
|
@ -282,8 +294,10 @@ class Event extends MatrixEvent {
|
||||||
|
|
||||||
/// Returns a list of [Receipt] instances for this event.
|
/// Returns a list of [Receipt] instances for this event.
|
||||||
List<Receipt> get receipts {
|
List<Receipt> get receipts {
|
||||||
if (!(room.roomAccountData.containsKey('m.receipt'))) return [];
|
final room = this.room;
|
||||||
return room.roomAccountData['m.receipt'].content.entries
|
final receipt = room?.roomAccountData['m.receipt'];
|
||||||
|
if (receipt == null || room == null) return [];
|
||||||
|
return receipt.content.entries
|
||||||
.where((entry) => entry.value['event_id'] == eventId)
|
.where((entry) => entry.value['event_id'] == eventId)
|
||||||
.map((entry) => Receipt(room.getUserByMXIDSync(entry.key),
|
.map((entry) => Receipt(room.getUserByMXIDSync(entry.key),
|
||||||
DateTime.fromMillisecondsSinceEpoch(entry.value['ts'])))
|
DateTime.fromMillisecondsSinceEpoch(entry.value['ts'])))
|
||||||
|
|
@ -294,6 +308,11 @@ class Event extends MatrixEvent {
|
||||||
/// This event will just be removed from the database and the timelines.
|
/// This event will just be removed from the database and the timelines.
|
||||||
/// Returns [false] if not removed.
|
/// Returns [false] if not removed.
|
||||||
Future<bool> remove() async {
|
Future<bool> remove() async {
|
||||||
|
final room = this.room;
|
||||||
|
if (room == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!status.isSent) {
|
if (!status.isSent) {
|
||||||
await room.client.database?.removeEvent(eventId, room.id);
|
await room.client.database?.removeEvent(eventId, room.id);
|
||||||
|
|
||||||
|
|
@ -311,29 +330,33 @@ class Event extends MatrixEvent {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to send this event again. Only works with events of `EventStatus.isError`.
|
/// Try to send this event again. Only works with events of status -1.
|
||||||
Future<String> sendAgain({String txid}) async {
|
Future<String?> sendAgain({String? txid}) async {
|
||||||
if (!status.isError) 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(
|
||||||
content,
|
content,
|
||||||
txid: txid ?? unsigned['transaction_id'] ?? eventId,
|
txid: txid ?? unsigned?['transaction_id'] ?? eventId,
|
||||||
);
|
);
|
||||||
return newEventId;
|
return newEventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the client is allowed to redact this event.
|
/// Whether the client is allowed to redact this event.
|
||||||
bool get canRedact => senderId == room.client.userID || room.canRedact;
|
bool get canRedact =>
|
||||||
|
senderId == room?.client.userID || (room?.canRedact ?? false);
|
||||||
|
|
||||||
/// Redacts this event. Throws `ErrorResponse` on error.
|
/// Redacts this event. Throws `ErrorResponse` on error.
|
||||||
Future<dynamic> redactEvent({String reason, String txid}) =>
|
Future<String?> redactEvent({String? reason, String? txid}) async =>
|
||||||
room.redactEvent(eventId, reason: reason, txid: txid);
|
await room?.redactEvent(eventId, reason: reason, txid: txid);
|
||||||
|
|
||||||
/// Searches for the reply event in the given timeline.
|
/// Searches for the reply event in the given timeline.
|
||||||
Future<Event> getReplyEvent(Timeline timeline) async {
|
Future<Event?> getReplyEvent(Timeline timeline) async {
|
||||||
if (relationshipType != RelationshipTypes.reply) return null;
|
if (relationshipType != RelationshipTypes.reply) return null;
|
||||||
return await timeline.getEventById(relationshipEventId);
|
final relationshipEventId = this.relationshipEventId;
|
||||||
|
return relationshipEventId == null
|
||||||
|
? null
|
||||||
|
: await timeline.getEventById(relationshipEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this event is encrypted and the decryption was not successful because
|
/// If this event is encrypted and the decryption was not successful because
|
||||||
|
|
@ -346,7 +369,7 @@ class Event extends MatrixEvent {
|
||||||
content['can_request_session'] != true) {
|
content['can_request_session'] != true) {
|
||||||
throw ('Session key not requestable');
|
throw ('Session key not requestable');
|
||||||
}
|
}
|
||||||
await room.requestSessionKey(content['session_id'], content['sender_key']);
|
await room?.requestSessionKey(content['session_id'], content['sender_key']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,7 +443,7 @@ class Event extends MatrixEvent {
|
||||||
/// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k
|
/// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k
|
||||||
/// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment.
|
/// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment.
|
||||||
/// [animated] says weather the thumbnail is animated
|
/// [animated] says weather the thumbnail is animated
|
||||||
Uri getAttachmentUrl(
|
Uri? getAttachmentUrl(
|
||||||
{bool getThumbnail = false,
|
{bool getThumbnail = false,
|
||||||
bool useThumbnailMxcUrl = false,
|
bool useThumbnailMxcUrl = false,
|
||||||
double width = 800.0,
|
double width = 800.0,
|
||||||
|
|
@ -428,6 +451,10 @@ class Event extends MatrixEvent {
|
||||||
ThumbnailMethod method = ThumbnailMethod.scale,
|
ThumbnailMethod method = ThumbnailMethod.scale,
|
||||||
int minNoThumbSize = _minNoThumbSize,
|
int minNoThumbSize = _minNoThumbSize,
|
||||||
bool animated = false}) {
|
bool animated = false}) {
|
||||||
|
final client = room?.client;
|
||||||
|
if (client == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (![EventTypes.Message, EventTypes.Sticker].contains(type) ||
|
if (![EventTypes.Message, EventTypes.Sticker].contains(type) ||
|
||||||
!hasAttachment ||
|
!hasAttachment ||
|
||||||
isAttachmentEncrypted) {
|
isAttachmentEncrypted) {
|
||||||
|
|
@ -449,14 +476,14 @@ class Event extends MatrixEvent {
|
||||||
// now generate the actual URLs
|
// now generate the actual URLs
|
||||||
if (getThumbnail) {
|
if (getThumbnail) {
|
||||||
return Uri.parse(thisMxcUrl).getThumbnail(
|
return Uri.parse(thisMxcUrl).getThumbnail(
|
||||||
room.client,
|
client,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
method: method,
|
method: method,
|
||||||
animated: animated,
|
animated: animated,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Uri.parse(thisMxcUrl).getDownloadLink(room.client);
|
return Uri.parse(thisMxcUrl).getDownloadLink(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,13 +499,17 @@ class Event extends MatrixEvent {
|
||||||
getThumbnail = mxcUrl != attachmentMxcUrl;
|
getThumbnail = mxcUrl != attachmentMxcUrl;
|
||||||
// Is this file storeable?
|
// Is this file storeable?
|
||||||
final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap;
|
final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap;
|
||||||
final storeable = room.client.database != null &&
|
final database = room?.client.database;
|
||||||
thisInfoMap['size'] is int &&
|
if (database == null) {
|
||||||
thisInfoMap['size'] <= room.client.database.maxFileSize;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Uint8List uint8list;
|
final storeable = thisInfoMap['size'] is int &&
|
||||||
|
thisInfoMap['size'] <= database.maxFileSize;
|
||||||
|
|
||||||
|
Uint8List? uint8list;
|
||||||
if (storeable) {
|
if (storeable) {
|
||||||
uint8list = await room.client.database.getFile(mxcUrl);
|
uint8list = await database.getFile(mxcUrl);
|
||||||
}
|
}
|
||||||
return uint8list != null;
|
return uint8list != null;
|
||||||
}
|
}
|
||||||
|
|
@ -489,10 +520,15 @@ class Event extends MatrixEvent {
|
||||||
/// true to download the thumbnail instead.
|
/// true to download the thumbnail instead.
|
||||||
Future<MatrixFile> downloadAndDecryptAttachment(
|
Future<MatrixFile> downloadAndDecryptAttachment(
|
||||||
{bool getThumbnail = false,
|
{bool getThumbnail = false,
|
||||||
Future<Uint8List> Function(Uri) downloadCallback}) async {
|
Future<Uint8List> Function(Uri)? downloadCallback}) async {
|
||||||
if (![EventTypes.Message, EventTypes.Sticker].contains(type)) {
|
if (![EventTypes.Message, EventTypes.Sticker].contains(type)) {
|
||||||
throw ("This event has the type '$type' and so it can't contain an attachment.");
|
throw ("This event has the type '$type' and so it can't contain an attachment.");
|
||||||
}
|
}
|
||||||
|
final client = room?.client;
|
||||||
|
final database = room?.client.database;
|
||||||
|
if (client == null) {
|
||||||
|
throw 'This event has no valid client.';
|
||||||
|
}
|
||||||
final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail);
|
final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail);
|
||||||
if (mxcUrl == null) {
|
if (mxcUrl == null) {
|
||||||
throw "This event hasn't any attachment or thumbnail.";
|
throw "This event hasn't any attachment or thumbnail.";
|
||||||
|
|
@ -500,33 +536,30 @@ class Event extends MatrixEvent {
|
||||||
getThumbnail = mxcUrl != attachmentMxcUrl;
|
getThumbnail = mxcUrl != attachmentMxcUrl;
|
||||||
final isEncrypted =
|
final isEncrypted =
|
||||||
getThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted;
|
getThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted;
|
||||||
|
if (isEncrypted && !client.encryptionEnabled) {
|
||||||
if (isEncrypted && !room.client.encryptionEnabled) {
|
|
||||||
throw ('Encryption is not enabled in your Client.');
|
throw ('Encryption is not enabled in your Client.');
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List uint8list;
|
|
||||||
|
|
||||||
// Is this file storeable?
|
// Is this file storeable?
|
||||||
final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap;
|
final thisInfoMap = getThumbnail ? thumbnailInfoMap : infoMap;
|
||||||
var storeable = room.client.database != null &&
|
var storeable = database != null &&
|
||||||
thisInfoMap['size'] is int &&
|
thisInfoMap['size'] is int &&
|
||||||
thisInfoMap['size'] <= room.client.database.maxFileSize;
|
thisInfoMap['size'] <= database.maxFileSize;
|
||||||
|
|
||||||
|
Uint8List? uint8list;
|
||||||
if (storeable) {
|
if (storeable) {
|
||||||
uint8list = await room.client.database.getFile(mxcUrl);
|
uint8list = await client.database.getFile(mxcUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the file
|
// Download the file
|
||||||
if (uint8list == null) {
|
if (uint8list == null) {
|
||||||
downloadCallback ??= (Uri url) async {
|
downloadCallback ??= (Uri url) async => (await http.get(url)).bodyBytes;
|
||||||
return (await http.get(url)).bodyBytes;
|
uint8list = await downloadCallback(mxcUrl.getDownloadLink(client));
|
||||||
};
|
storeable = database != null &&
|
||||||
uint8list = await downloadCallback(mxcUrl.getDownloadLink(room.client));
|
storeable &&
|
||||||
storeable = storeable &&
|
uint8list.lengthInBytes < database.maxFileSize;
|
||||||
uint8list.lengthInBytes < room.client.database.maxFileSize;
|
|
||||||
if (storeable) {
|
if (storeable) {
|
||||||
await room.client.database.storeFile(
|
await database.storeFile(
|
||||||
mxcUrl, uint8list, DateTime.now().millisecondsSinceEpoch);
|
mxcUrl, uint8list, DateTime.now().millisecondsSinceEpoch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,7 +577,7 @@ class Event extends MatrixEvent {
|
||||||
k: fileMap['key']['k'],
|
k: fileMap['key']['k'],
|
||||||
sha256: fileMap['hashes']['sha256'],
|
sha256: fileMap['hashes']['sha256'],
|
||||||
);
|
);
|
||||||
uint8list = await room.client.runInBackground(decryptFile, encryptedFile);
|
uint8list = await client.runInBackground(decryptFile, encryptedFile);
|
||||||
}
|
}
|
||||||
return MatrixFile(bytes: uint8list, name: body);
|
return MatrixFile(bytes: uint8list, name: body);
|
||||||
}
|
}
|
||||||
|
|
@ -565,7 +598,7 @@ class Event extends MatrixEvent {
|
||||||
bool plaintextBody = false,
|
bool plaintextBody = false,
|
||||||
}) {
|
}) {
|
||||||
if (redacted) {
|
if (redacted) {
|
||||||
return i18n.removedBy(redactedBecause.sender.calcDisplayname());
|
return i18n.removedBy(redactedBecause?.sender?.calcDisplayname() ?? '');
|
||||||
}
|
}
|
||||||
var body = plaintextBody ? this.plaintextBody : this.body;
|
var body = plaintextBody ? this.plaintextBody : this.body;
|
||||||
|
|
||||||
|
|
@ -607,8 +640,9 @@ class Event extends MatrixEvent {
|
||||||
if (withSenderNamePrefix &&
|
if (withSenderNamePrefix &&
|
||||||
type == EventTypes.Message &&
|
type == EventTypes.Message &&
|
||||||
textOnlyMessageTypes.contains(messageType)) {
|
textOnlyMessageTypes.contains(messageType)) {
|
||||||
final senderNameOrYou =
|
final senderNameOrYou = senderId == room?.client.userID
|
||||||
senderId == room.client.userID ? i18n.you : sender.calcDisplayname();
|
? i18n.you
|
||||||
|
: (sender?.calcDisplayname() ?? '');
|
||||||
localizedBody = '$senderNameOrYou: $localizedBody';
|
localizedBody = '$senderNameOrYou: $localizedBody';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -623,18 +657,18 @@ class Event extends MatrixEvent {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// returns if this event matches the passed event or transaction id
|
/// returns if this event matches the passed event or transaction id
|
||||||
bool matchesEventOrTransactionId(String search) {
|
bool matchesEventOrTransactionId(String? search) {
|
||||||
if (search == null) {
|
if (search == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (eventId == search) {
|
if (eventId == search) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return unsigned != null && unsigned['transaction_id'] == search;
|
return unsigned?['transaction_id'] == search;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the relationship type of an event. `null` if there is none
|
/// Get the relationship type of an event. `null` if there is none
|
||||||
String get relationshipType {
|
String? get relationshipType {
|
||||||
if (content?.tryGet<Map<String, dynamic>>('m.relates_to') == null) {
|
if (content?.tryGet<Map<String, dynamic>>('m.relates_to') == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -643,11 +677,11 @@ class Event extends MatrixEvent {
|
||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
.tryGet<Map<String, dynamic>>('m.relates_to')
|
.tryGet<Map<String, dynamic>>('m.relates_to')
|
||||||
.tryGet<String>('rel_type');
|
?.tryGet<String>('rel_type');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the event ID that this relationship will reference. `null` if there is none
|
/// Get the event ID that this relationship will reference. `null` if there is none
|
||||||
String get relationshipEventId {
|
String? get relationshipEventId {
|
||||||
if (content == null || !(content['m.relates_to'] is Map)) {
|
if (content == null || !(content['m.relates_to'] is Map)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -664,15 +698,12 @@ class Event extends MatrixEvent {
|
||||||
/// Get whether 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[eventId]?.containsKey(type) == true;
|
||||||
timeline.aggregatedEvents[eventId].containsKey(type);
|
|
||||||
|
|
||||||
/// Get all the aggregated event objects for a given [type]. To be able to do this
|
/// Get all the aggregated event objects for a given [type]. To be able to do this
|
||||||
/// you have to pass a [timeline]
|
/// you have to pass a [timeline]
|
||||||
Set<Event> aggregatedEvents(Timeline timeline, String type) =>
|
Set<Event> aggregatedEvents(Timeline timeline, String type) =>
|
||||||
hasAggregatedEvents(timeline, type)
|
timeline.aggregatedEvents[eventId]?[type] ?? <Event>{};
|
||||||
? timeline.aggregatedEvents[eventId][type]
|
|
||||||
: <Event>{};
|
|
||||||
|
|
||||||
/// Fetches the event to be rendered, taking into account all the edits and the like.
|
/// Fetches the event to be rendered, taking into account all the edits and the like.
|
||||||
/// It needs a [timeline] for that.
|
/// It needs a [timeline] for that.
|
||||||
|
|
|
||||||
|
|
@ -190,9 +190,10 @@ class Timeline {
|
||||||
if (events[i].eventId != null) {
|
if (events[i].eventId != null) {
|
||||||
searchHaystack.add(events[i].eventId);
|
searchHaystack.add(events[i].eventId);
|
||||||
}
|
}
|
||||||
if (events[i].unsigned != null &&
|
|
||||||
events[i].unsigned['transaction_id'] != null) {
|
final txnid = events[i].unsigned?['transaction_id'];
|
||||||
searchHaystack.add(events[i].unsigned['transaction_id']);
|
if (txnid != null) {
|
||||||
|
searchHaystack.add(txnid);
|
||||||
}
|
}
|
||||||
if (searchNeedle.intersection(searchHaystack).isNotEmpty) {
|
if (searchNeedle.intersection(searchHaystack).isNotEmpty) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -205,19 +206,18 @@ class Timeline {
|
||||||
eventSet.removeWhere((e) =>
|
eventSet.removeWhere((e) =>
|
||||||
e.matchesEventOrTransactionId(event.eventId) ||
|
e.matchesEventOrTransactionId(event.eventId) ||
|
||||||
(event.unsigned != null &&
|
(event.unsigned != null &&
|
||||||
e.matchesEventOrTransactionId(event.unsigned['transaction_id'])));
|
e.matchesEventOrTransactionId(event.unsigned?['transaction_id'])));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAggregatedEvent(Event event) {
|
void addAggregatedEvent(Event event) {
|
||||||
// we want to add an event to the aggregation tree
|
// we want to add an event to the aggregation tree
|
||||||
if (event.relationshipType == null || event.relationshipEventId == null) {
|
final relationshipType = event.relationshipType;
|
||||||
|
final relationshipEventId = event.relationshipEventId;
|
||||||
|
if (relationshipType == null || relationshipEventId == null) {
|
||||||
return; // nothing to do
|
return; // nothing to do
|
||||||
}
|
}
|
||||||
if (!aggregatedEvents.containsKey(event.relationshipEventId)) {
|
final events = (aggregatedEvents[relationshipEventId] ??=
|
||||||
aggregatedEvents[event.relationshipEventId] = <String, Set<Event>>{};
|
<String, Set<Event>>{})[relationshipType] ??= <Event>{};
|
||||||
}
|
|
||||||
final events = (aggregatedEvents[event.relationshipEventId] ??=
|
|
||||||
<String, Set<Event>>{})[event.relationshipType] ??= <Event>{};
|
|
||||||
// remove a potential old event
|
// remove a potential old event
|
||||||
_removeEventFromSet(events, event);
|
_removeEventFromSet(events, event);
|
||||||
// add the new one
|
// add the new one
|
||||||
|
|
@ -227,7 +227,7 @@ class Timeline {
|
||||||
void removeAggregatedEvent(Event event) {
|
void removeAggregatedEvent(Event event) {
|
||||||
aggregatedEvents.remove(event.eventId);
|
aggregatedEvents.remove(event.eventId);
|
||||||
if (event.unsigned != null) {
|
if (event.unsigned != null) {
|
||||||
aggregatedEvents.remove(event.unsigned['transaction_id']);
|
aggregatedEvents.remove(event.unsigned?['transaction_id']);
|
||||||
}
|
}
|
||||||
for (final types in aggregatedEvents.values) {
|
for (final types in aggregatedEvents.values) {
|
||||||
for (final events in types.values) {
|
for (final events in types.values) {
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ class User extends Event {
|
||||||
required String stateKey,
|
required String stateKey,
|
||||||
dynamic content,
|
dynamic content,
|
||||||
required String typeKey,
|
required String typeKey,
|
||||||
String? eventId,
|
String eventId = 'fakevent',
|
||||||
String? roomId,
|
String? roomId,
|
||||||
String? senderId,
|
String senderId = 'fakesender',
|
||||||
required DateTime originServerTs,
|
required DateTime originServerTs,
|
||||||
dynamic unsigned,
|
dynamic unsigned,
|
||||||
Room? room})
|
Room? room})
|
||||||
|
|
@ -68,7 +68,7 @@ class User extends Event {
|
||||||
room: room);
|
room: room);
|
||||||
|
|
||||||
/// The full qualified Matrix ID in the format @username:server.abc.
|
/// The full qualified Matrix ID in the format @username:server.abc.
|
||||||
String get id => stateKey;
|
String get id => stateKey ?? '\@unknown:unknown';
|
||||||
|
|
||||||
/// The displayname of the user if the user has set one.
|
/// The displayname of the user if the user has set one.
|
||||||
String? get displayName =>
|
String? get displayName =>
|
||||||
|
|
@ -91,13 +91,16 @@ class User extends Event {
|
||||||
}, orElse: () => Membership.join);
|
}, orElse: () => Membership.join);
|
||||||
|
|
||||||
/// The avatar if the user has one.
|
/// The avatar if the user has one.
|
||||||
Uri? get avatarUrl => content != null && content.containsKey('avatar_url')
|
Uri? get avatarUrl {
|
||||||
? (content['avatar_url'] is String
|
final prevContent = this.prevContent;
|
||||||
? Uri.tryParse(content['avatar_url'])
|
return content.containsKey('avatar_url')
|
||||||
: null)
|
? (content['avatar_url'] is String
|
||||||
: (prevContent != null && prevContent['avatar_url'] is String
|
? Uri.tryParse(content['avatar_url'])
|
||||||
? Uri.tryParse(prevContent['avatar_url'])
|
: null)
|
||||||
: 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
|
/// 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
|
/// has no displayname. If [formatLocalpart] is true, then the localpart will
|
||||||
|
|
@ -132,37 +135,40 @@ class User extends Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to kick this user from this room.
|
/// Call the Matrix API to kick this user from this room.
|
||||||
Future<void> kick() => room.kick(id);
|
Future<void> kick() async => await room?.kick(id);
|
||||||
|
|
||||||
/// Call the Matrix API to ban this user from this room.
|
/// Call the Matrix API to ban this user from this room.
|
||||||
Future<void> ban() => room.ban(id);
|
Future<void> ban() async => await room?.ban(id);
|
||||||
|
|
||||||
/// Call the Matrix API to unban this banned user from this room.
|
/// Call the Matrix API to unban this banned user from this room.
|
||||||
Future<void> unban() => room.unban(id);
|
Future<void> unban() async => await room?.unban(id);
|
||||||
|
|
||||||
/// Call the Matrix API to change the power level of this user.
|
/// Call the Matrix API to change the power level of this user.
|
||||||
Future<void> setPower(int power) => room.setPower(id, power);
|
Future<void> setPower(int power) async => await room?.setPower(id, power);
|
||||||
|
|
||||||
/// Returns an existing direct chat ID with this user or creates a new one.
|
/// Returns an existing direct chat ID with this user or creates a new one.
|
||||||
/// Returns null on error.
|
/// Returns null on error.
|
||||||
Future<String?> startDirectChat() => room.client.startDirectChat(id);
|
Future<String?> startDirectChat() async => room?.client.startDirectChat(id);
|
||||||
|
|
||||||
/// The newest presence of this user if there is any and null if not.
|
/// The newest presence of this user if there is any and null if not.
|
||||||
Presence? get presence => room.client.presences[id];
|
Presence? get presence => room?.client.presences[id];
|
||||||
|
|
||||||
/// Whether the client is able to ban/unban this user.
|
/// Whether the client is able to ban/unban this user.
|
||||||
bool get canBan => room.canBan && powerLevel < room.ownPowerLevel;
|
bool get canBan =>
|
||||||
|
(room?.canBan ?? false) &&
|
||||||
|
powerLevel < (room?.ownPowerLevel ?? powerLevel);
|
||||||
|
|
||||||
/// Whether the client is able to kick this user.
|
/// Whether the client is able to kick this user.
|
||||||
bool get canKick =>
|
bool get canKick =>
|
||||||
[Membership.join, Membership.invite].contains(membership) &&
|
[Membership.join, Membership.invite].contains(membership) &&
|
||||||
room.canKick &&
|
(room?.canKick ?? false) &&
|
||||||
powerLevel < room.ownPowerLevel;
|
powerLevel < (room?.ownPowerLevel ?? powerLevel);
|
||||||
|
|
||||||
/// Whether the client is allowed to change the power level of this user.
|
/// 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!
|
/// Please be aware that you can only set the power level to at least your own!
|
||||||
bool get canChangePowerLevel =>
|
bool get canChangePowerLevel =>
|
||||||
room.canChangePowerLevel && powerLevel < room.ownPowerLevel;
|
(room?.canChangePowerLevel ?? false) &&
|
||||||
|
powerLevel < (room?.ownPowerLevel ?? powerLevel);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) => (other is User &&
|
bool operator ==(dynamic other) => (other is User &&
|
||||||
|
|
@ -190,7 +196,7 @@ class User extends Event {
|
||||||
: '[$displayName]');
|
: '[$displayName]');
|
||||||
|
|
||||||
// get all the users with the same display name
|
// get all the users with the same display name
|
||||||
final allUsersWithSameDisplayname = room.getParticipants();
|
final allUsersWithSameDisplayname = room?.getParticipants() ?? [];
|
||||||
allUsersWithSameDisplayname.removeWhere((user) =>
|
allUsersWithSameDisplayname.removeWhere((user) =>
|
||||||
user.id == id ||
|
user.id == id ||
|
||||||
(user.displayName?.isEmpty ?? true) ||
|
(user.displayName?.isEmpty ?? true) ||
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,11 @@ abstract class EventLocalizations {
|
||||||
},
|
},
|
||||||
EventTypes.RoomMember: (event, i18n, body) {
|
EventTypes.RoomMember: (event, i18n, body) {
|
||||||
var text = 'Failed to parse member event';
|
var text = 'Failed to parse member event';
|
||||||
final targetName = event.stateKeyUser.calcDisplayname();
|
final targetName = event.stateKeyUser?.calcDisplayname() ?? '';
|
||||||
// Has the membership changed?
|
// Has the membership changed?
|
||||||
final newMembership = event.content['membership'] ?? '';
|
final newMembership = event.content['membership'] ?? '';
|
||||||
final oldMembership = event.prevContent != null
|
final oldMembership = event.prevContent?['membership'] ?? '';
|
||||||
? event.prevContent['membership'] ?? ''
|
|
||||||
: '';
|
|
||||||
if (newMembership != oldMembership) {
|
if (newMembership != oldMembership) {
|
||||||
if (oldMembership == 'invite' && newMembership == 'join') {
|
if (oldMembership == 'invite' && newMembership == 'join') {
|
||||||
text = i18n.acceptedTheInvitation(targetName);
|
text = i18n.acceptedTheInvitation(targetName);
|
||||||
|
|
@ -146,22 +145,19 @@ abstract class EventLocalizations {
|
||||||
}
|
}
|
||||||
} else if (newMembership == 'join') {
|
} else if (newMembership == 'join') {
|
||||||
final newAvatar = event.content['avatar_url'] ?? '';
|
final newAvatar = event.content['avatar_url'] ?? '';
|
||||||
final oldAvatar = event.prevContent != null
|
final oldAvatar = event.prevContent?['avatar_url'] ?? '';
|
||||||
? event.prevContent['avatar_url'] ?? ''
|
|
||||||
: '';
|
|
||||||
|
|
||||||
final newDisplayname = event.content['displayname'] ?? '';
|
final newDisplayname = event.content['displayname'] ?? '';
|
||||||
final oldDisplayname = event.prevContent != null
|
final oldDisplayname = event.prevContent?['displayname'] ?? '';
|
||||||
? event.prevContent['displayname'] ?? ''
|
final stateKey = event.stateKey;
|
||||||
: '';
|
|
||||||
|
|
||||||
// Has the user avatar changed?
|
// Has the user avatar changed?
|
||||||
if (newAvatar != oldAvatar) {
|
if (newAvatar != oldAvatar) {
|
||||||
text = i18n.changedTheProfileAvatar(targetName);
|
text = i18n.changedTheProfileAvatar(targetName);
|
||||||
}
|
}
|
||||||
// Has the user avatar changed?
|
// Has the user displayname changed?
|
||||||
else if (newDisplayname != oldDisplayname) {
|
else if (newDisplayname != oldDisplayname && stateKey != null) {
|
||||||
text = i18n.changedTheDisplaynameTo(event.stateKey, newDisplayname);
|
text = i18n.changedTheDisplaynameTo(stateKey, newDisplayname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
|
|
@ -201,7 +197,7 @@ abstract class EventLocalizations {
|
||||||
EventTypes.Encryption: (event, i18n, body) {
|
EventTypes.Encryption: (event, i18n, body) {
|
||||||
var localizedBody =
|
var localizedBody =
|
||||||
i18n.activatedEndToEndEncryption(event.sender.calcDisplayname());
|
i18n.activatedEndToEndEncryption(event.sender.calcDisplayname());
|
||||||
if (!event.room.client.encryptionEnabled) {
|
if (event.room?.client.encryptionEnabled == false) {
|
||||||
localizedBody += '. ' + i18n.needPantalaimonWarning;
|
localizedBody += '. ' + i18n.needPantalaimonWarning;
|
||||||
}
|
}
|
||||||
return localizedBody;
|
return localizedBody;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,9 @@ extension ImagePackRoomExtension on Room {
|
||||||
for (final entry in allRoomEmotes.entries) {
|
for (final entry in allRoomEmotes.entries) {
|
||||||
addImagePack(entry.value,
|
addImagePack(entry.value,
|
||||||
room: this,
|
room: this,
|
||||||
slug: entry.value.stateKey.isEmpty ? 'room' : entry.value.stateKey);
|
slug: (entry.value.stateKey?.isNotEmpty == true)
|
||||||
|
? entry.value.stateKey
|
||||||
|
: 'room');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packs;
|
return packs;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import 'package:matrix_api_lite/matrix_api_lite.dart';
|
||||||
import '../event.dart';
|
import '../event.dart';
|
||||||
|
|
||||||
class SpaceChild {
|
class SpaceChild {
|
||||||
final String roomId;
|
final String? roomId;
|
||||||
final List<String>? via;
|
final List<String>? via;
|
||||||
final String order;
|
final String order;
|
||||||
final bool? suggested;
|
final bool? suggested;
|
||||||
|
|
@ -35,7 +35,7 @@ class SpaceChild {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaceParent {
|
class SpaceParent {
|
||||||
final String roomId;
|
final String? roomId;
|
||||||
final List<String>? via;
|
final List<String>? via;
|
||||||
final bool? canonical;
|
final bool? canonical;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,18 @@ void main() {
|
||||||
content: {},
|
content: {},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
eventId: '\$fakeeventid',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
));
|
));
|
||||||
room.setState(Event(
|
room.setState(Event(
|
||||||
type: 'm.room.member',
|
type: 'm.room.member',
|
||||||
content: {'membership': 'join'},
|
content: {'membership': 'join'},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: client.userID,
|
stateKey: client.userID,
|
||||||
|
eventId: '\$fakeeventid',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -135,7 +141,18 @@ void main() {
|
||||||
test('react', () async {
|
test('react', () async {
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await room.sendTextEvent('/react 🦊',
|
await room.sendTextEvent('/react 🦊',
|
||||||
inReplyTo: Event(eventId: '\$event'));
|
inReplyTo: Event(
|
||||||
|
eventId: '\$event',
|
||||||
|
type: 'm.room.message',
|
||||||
|
content: {
|
||||||
|
'msgtype': 'm.text',
|
||||||
|
'body': '<b>yay</b>',
|
||||||
|
'format': 'org.matrix.custom.html',
|
||||||
|
'formatted_body': '<b>yay</b>',
|
||||||
|
},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
senderId: client.userID,
|
||||||
|
));
|
||||||
final sent = getLastMessagePayload('m.reaction');
|
final sent = getLastMessagePayload('m.reaction');
|
||||||
expect(sent, {
|
expect(sent, {
|
||||||
'm.relates_to': {
|
'm.relates_to': {
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ void main() {
|
||||||
room: room,
|
room: room,
|
||||||
originServerTs: now,
|
originServerTs: now,
|
||||||
eventId: '\$event',
|
eventId: '\$event',
|
||||||
|
senderId: client.userID,
|
||||||
);
|
);
|
||||||
final decryptedEvent =
|
final decryptedEvent =
|
||||||
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
|
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
|
||||||
|
|
|
||||||
|
|
@ -36,24 +36,36 @@ void main() {
|
||||||
content: {},
|
content: {},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: client.userID,
|
||||||
|
eventId: '\$fakeid1:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
room.setState(Event(
|
room.setState(Event(
|
||||||
type: 'm.room.member',
|
type: 'm.room.member',
|
||||||
content: {'membership': 'join'},
|
content: {'membership': 'join'},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: client.userID,
|
stateKey: client.userID,
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid2:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
room2.setState(Event(
|
room2.setState(Event(
|
||||||
type: 'm.room.power_levels',
|
type: 'm.room.power_levels',
|
||||||
content: {},
|
content: {},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: client.userID,
|
||||||
|
eventId: '\$fakeid3:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
room2.setState(Event(
|
room2.setState(Event(
|
||||||
type: 'm.room.member',
|
type: 'm.room.member',
|
||||||
content: {'membership': 'join'},
|
content: {'membership': 'join'},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: client.userID,
|
stateKey: client.userID,
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid4:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
client.rooms.add(room);
|
client.rooms.add(room);
|
||||||
client.rooms.add(room2);
|
client.rooms.add(room2);
|
||||||
|
|
@ -69,6 +81,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid5:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
final packs = room.getImagePacks();
|
final packs = room.getImagePacks();
|
||||||
expect(packs.length, 1);
|
expect(packs.length, 1);
|
||||||
|
|
@ -95,6 +110,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid6:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
||||||
expect(packsFlat, {
|
expect(packsFlat, {
|
||||||
|
|
@ -117,6 +135,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid7:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
||||||
expect(packsFlat, {
|
expect(packsFlat, {
|
||||||
|
|
@ -137,6 +158,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room,
|
room: room,
|
||||||
stateKey: 'fox',
|
stateKey: 'fox',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid8:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
packsFlat = room.getImagePacksFlat(ImagePackUsage.emoticon);
|
||||||
expect(packsFlat, {
|
expect(packsFlat, {
|
||||||
|
|
@ -177,6 +201,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room2,
|
room: room2,
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid9:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({
|
client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({
|
||||||
'type': 'im.ponies.emote_rooms',
|
'type': 'im.ponies.emote_rooms',
|
||||||
|
|
@ -207,6 +234,9 @@ void main() {
|
||||||
},
|
},
|
||||||
room: room2,
|
room: room2,
|
||||||
stateKey: 'fox',
|
stateKey: 'fox',
|
||||||
|
senderId: '\@fakeuser:fakeServer.notExisting',
|
||||||
|
eventId: '\$fakeid10:fakeServer.notExisting',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
));
|
));
|
||||||
client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({
|
client.accountData['im.ponies.emote_rooms'] = BasicEvent.fromJson({
|
||||||
'type': 'im.ponies.emote_rooms',
|
'type': 'im.ponies.emote_rooms',
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ void main() {
|
||||||
room: room,
|
room: room,
|
||||||
eventId: '123',
|
eventId: '123',
|
||||||
content: {'alias': '#testalias:example.com'},
|
content: {'alias': '#testalias:example.com'},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.displayname, 'testalias');
|
expect(room.displayname, 'testalias');
|
||||||
|
|
@ -120,6 +121,7 @@ void main() {
|
||||||
room: room,
|
room: room,
|
||||||
eventId: '123',
|
eventId: '123',
|
||||||
content: {'name': 'testname'},
|
content: {'name': 'testname'},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.displayname, 'testname');
|
expect(room.displayname, 'testname');
|
||||||
|
|
@ -133,6 +135,7 @@ void main() {
|
||||||
room: room,
|
room: room,
|
||||||
eventId: '123',
|
eventId: '123',
|
||||||
content: {'topic': 'testtopic'},
|
content: {'topic': 'testtopic'},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.topic, 'testtopic');
|
expect(room.topic, 'testtopic');
|
||||||
|
|
@ -146,6 +149,7 @@ void main() {
|
||||||
room: room,
|
room: room,
|
||||||
eventId: '123',
|
eventId: '123',
|
||||||
content: {'url': 'mxc://testurl'},
|
content: {'url': 'mxc://testurl'},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.avatar.toString(), 'mxc://testurl');
|
expect(room.avatar.toString(), 'mxc://testurl');
|
||||||
|
|
@ -161,6 +165,7 @@ void main() {
|
||||||
content: {
|
content: {
|
||||||
'pinned': ['1234']
|
'pinned': ['1234']
|
||||||
},
|
},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.pinnedEventIds.first, '1234');
|
expect(room.pinnedEventIds.first, '1234');
|
||||||
|
|
@ -358,6 +363,7 @@ void main() {
|
||||||
'users': {'@test:fakeServer.notExisting': 100},
|
'users': {'@test:fakeServer.notExisting': 100},
|
||||||
'users_default': 10
|
'users_default': 10
|
||||||
},
|
},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: ''),
|
stateKey: ''),
|
||||||
);
|
);
|
||||||
expect(room.ownPowerLevel, 100);
|
expect(room.ownPowerLevel, 100);
|
||||||
|
|
@ -396,6 +402,7 @@ void main() {
|
||||||
'users': {},
|
'users': {},
|
||||||
'users_default': 0
|
'users_default': 0
|
||||||
},
|
},
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
stateKey: '',
|
stateKey: '',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue