...
 
Commits (10)
......@@ -13,7 +13,6 @@ import 'package:matrix_sdk/src/event/room/state/room_upgrade_event.dart';
import 'package:matrix_sdk/src/event/room/state/topic_change_event.dart';
import 'package:matrix_sdk/src/identifier.dart';
import 'package:matrix_sdk/src/util/json.dart';
import 'package:meta/meta.dart';
abstract class Event extends Identifiable<EventId> implements JsonWritable {
String get matrixType => matrixTypeOf(this);
......@@ -23,31 +22,29 @@ abstract class Event extends Identifiable<EventId> implements JsonWritable {
final SentState sentState = SentState.sent;
final UnsignedData unsigned;
EventContent get content;
Event(this.id, this.unsigned);
Event(this.id);
factory Event.fromContent(EventContent content, RoomEventArgs args) {
if (content is TextMessage) {
return TextMessageEvent(content, args);
return TextMessageEvent(args, content: content);
} else if (content is ImageMessage) {
return ImageMessageEvent(content, args);
return ImageMessageEvent(args, content: content);
} else if (content is AudioMessage) {
return AudioMessageEvent(content, args);
return AudioMessageEvent(args, content: content);
} else if (content is TopicChange) {
return TopicChangeEvent(content, args);
return TopicChangeEvent(args, content: content);
} else if (content is RoomNameChange) {
return RoomNameChangeEvent(content, args);
return RoomNameChangeEvent(args, content: content);
} else if (content is RoomAvatarChangeEvent) {
return RoomAvatarChangeEvent(content, args);
return RoomAvatarChangeEvent(args, content: content);
} else if (content is RoomUpgrade) {
return RoomUpgradeEvent(content, args);
return RoomUpgradeEvent(args, content: content);
} else if (content is PowerLevelsChange) {
return PowerLevelsChangeEvent(content, args);
return PowerLevelsChangeEvent(args, content: content);
} else if (content is RoomCreation) {
return RoomCreationEvent(content, args);
return RoomCreationEvent(args, content: content);
// TODO: Handle MemberChangeEvent
} else {
return null;
......@@ -60,7 +57,6 @@ abstract class Event extends Identifiable<EventId> implements JsonWritable {
'event_id': id.toJson(),
'type': matrixType,
'content': content?.toJson() ?? {},
'unsigned': unsigned?.toJson(),
};
}
......@@ -155,11 +151,3 @@ enum SentState { unsent, failed, sent }
abstract class EventContent {
Map<String, dynamic> toJson() => {};
}
class UnsignedData {
final String transactionId;
UnsignedData({@required this.transactionId});
dynamic toJson() => {'transaction_id': transactionId};
}
......@@ -39,7 +39,7 @@ abstract class MessageEvent extends RoomEvent {
@override
final MessageEventContent content;
MessageEvent(this.content, RoomEventArgs args) : super(args);
MessageEvent(RoomEventArgs args, {@required this.content}) : super(args);
static MessageEvent fromJson(RoomEventArgs args, Map<String, dynamic> json) {
final content = json['content'] as Map<String, dynamic>;
......@@ -80,13 +80,13 @@ abstract class MessageEvent extends RoomEvent {
url = tryParseMxcUrl(url);
return ImageMessageEvent(
ImageMessage(
args,
content: ImageMessage(
body: body,
url: url,
info: info,
inReplyToId: inReplyTo,
),
args,
);
case AudioMessage.type:
final body = content['body'];
......@@ -109,13 +109,13 @@ abstract class MessageEvent extends RoomEvent {
url = tryParseMxcUrl(url);
return AudioMessageEvent(
AudioMessage(
args,
content: AudioMessage(
body: body,
url: url,
info: info,
inReplyToId: inReplyTo,
),
args,
);
case TextMessage.type:
case EmoteMessage.type:
......@@ -125,21 +125,21 @@ abstract class MessageEvent extends RoomEvent {
if (msgtype == EmoteMessage.type) {
return EmoteMessageEvent(
EmoteMessage(
args,
content: EmoteMessage(
body: body,
formattedBody: formattedBody,
inReplyToId: inReplyTo,
),
args,
);
} else {
return TextMessageEvent(
TextMessage(
args,
content: TextMessage(
body: body,
formattedBody: formattedBody,
inReplyToId: inReplyTo,
),
args,
);
}
}
......@@ -214,7 +214,10 @@ class TextMessageEvent extends MessageEvent {
@override
final TextMessage content;
TextMessageEvent(this.content, RoomEventArgs args) : super(content, args);
TextMessageEvent(
RoomEventArgs args, {
@required this.content,
}) : super(args, content: content);
}
class EmoteMessage extends TextMessage {
......@@ -222,17 +225,25 @@ class EmoteMessage extends TextMessage {
@override
final String messageType = type;
EmoteMessage(
{@required String body, String formattedBody, EventId inReplyToId})
: super(
body: body, formattedBody: formattedBody, inReplyToId: inReplyToId);
EmoteMessage({
@required String body,
String formattedBody,
EventId inReplyToId,
}) : super(
body: body,
formattedBody: formattedBody,
inReplyToId: inReplyToId,
);
}
class EmoteMessageEvent extends TextMessageEvent {
@override
final EmoteMessage content;
EmoteMessageEvent(this.content, RoomEventArgs args) : super(content, args);
EmoteMessageEvent(
RoomEventArgs args, {
@required this.content,
}) : super(args, content: content);
}
class ImageMessage extends MessageEventContent {
......@@ -284,7 +295,10 @@ class ImageMessageEvent extends MessageEvent {
@override
final ImageMessage content;
ImageMessageEvent(this.content, RoomEventArgs args) : super(content, args);
ImageMessageEvent(
RoomEventArgs args, {
@required this.content,
}) : super(args, content: content);
}
class AudioMessage extends MessageEventContent {
......@@ -331,5 +345,8 @@ class AudioMessageEvent extends MessageEvent {
@override
final AudioMessage content;
AudioMessageEvent(this.content, RoomEventArgs args) : super(content, args);
AudioMessageEvent(
RoomEventArgs args, {
@required this.content,
}) : super(args, content: content);
}
......@@ -94,7 +94,10 @@ class RedactedEvent extends RoomEvent {
class RedactedStateEvent extends StateEvent implements RedactedEvent {
@override
final StateEventContent content = null;
final EventContent content = null;
@override
final EventContent previousContent = null;
@override
final String matrixType;
......@@ -108,10 +111,6 @@ class RedactedStateEvent extends StateEvent implements RedactedEvent {
super(RoomEventArgs.from(original), stateKey: original.stateKey);
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
'unsigned': {
'redacted_because': redaction.toJson(),
}
});
Map<String, dynamic> toJson() =>
super.toJson()..['unsigned']['redacted_because'] = redaction.toJson();
}
......@@ -30,18 +30,26 @@ abstract class RoomEvent extends Event {
@override
final SentState sentState;
final String transactionId;
RoomEvent(RoomEventArgs args)
: roomId = args.roomId,
sender = args.sender,
time = args.time,
sentState = args.sentState,
super(args.id, args.unsigned);
transactionId = args.transactionId,
super(args.id);
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
'sender': sender.id.toString(),
'origin_server_ts': time.millisecondsSinceEpoch
'origin_server_ts': time.millisecondsSinceEpoch,
'unsigned': transactionId != null
? {
'transaction_id': transactionId,
}
: {}
});
/// Create event from the given [json].
......@@ -126,7 +134,7 @@ abstract class RoomEvent extends Event {
}
if (event is MemberChangeEvent) {
context?.setUser(event.content.subject);
context?.setUser(event.subject);
}
return event;
......@@ -138,15 +146,15 @@ class RoomEventArgs {
final RoomId roomId;
final User sender;
final DateTime time;
final UnsignedData unsigned;
final SentState sentState;
final String transactionId;
RoomEventArgs({
this.id,
this.roomId,
this.sender,
this.time,
this.unsigned,
this.transactionId,
this.sentState = SentState.sent,
});
......@@ -156,7 +164,7 @@ class RoomEventArgs {
roomId: e.roomId,
sender: e.sender,
time: e.time,
unsigned: e.unsigned,
transactionId: e.transactionId,
sentState: e.sentState,
);
}
......@@ -166,15 +174,15 @@ class RoomEventArgs {
RoomId roomId,
User sender,
DateTime time,
UnsignedData unsigned,
SentState sentState,
String transactionId,
}) {
return RoomEventArgs(
id: id ?? this.id,
roomId: roomId ?? this.roomId,
sender: sender ?? this.sender,
time: time ?? this.time,
unsigned: unsigned ?? this.unsigned,
transactionId: transactionId ?? this.transactionId,
sentState: sentState ?? this.sentState,
);
}
......@@ -186,8 +194,8 @@ class RoomEventArgs {
RoomId roomId,
User sender,
DateTime time,
UnsignedData unsigned,
SentState sentState,
String transactionId,
}) async {
// These might be null
id = id ?? EventId(json['event_id']);
......@@ -220,14 +228,8 @@ class RoomEventArgs {
);
var unsignedJson = json['unsigned'];
if (unsigned == null && unsignedJson != null) {
final transactionId = json['unsigned']['transaction_id'];
if (transactionId != null) {
unsigned = UnsignedData(transactionId: transactionId);
} else {
unsigned = null;
}
if (transactionId == null && unsignedJson != null) {
transactionId = json['unsigned']['transaction_id'];
}
return this.copyWith(
......@@ -235,8 +237,8 @@ class RoomEventArgs {
roomId: roomId,
sender: sender,
time: time,
unsigned: unsigned,
sentState: sentState,
transactionId: transactionId,
);
}
}
......@@ -9,23 +9,55 @@ import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:meta/meta.dart';
import '../../../identifier.dart';
import '../../event.dart';
import '../../../util/map.dart';
class AliasesChangeEvent extends StateEvent {
class AliasesChangeEvent extends StateEvent implements HasDiff {
@override
final AliasesChange content;
@override
final AliasesChange previousContent;
@override
final AliasesChangeDiff diff;
AliasesChangeEvent(
this.content,
RoomEventArgs args, {
@required this.content,
this.previousContent,
@required String stateKey,
}) : super(args, stateKey: stateKey);
}) : diff = AliasesChangeDiff(previousContent, content),
super(args, stateKey: stateKey);
factory AliasesChangeEvent.fromJson(RoomEventArgs args, dynamic json) {
factory AliasesChangeEvent.fromJson(
RoomEventArgs args,
Map<String, dynamic> json,
) {
if (!hasStateKey(json)) {
return null;
}
final content = json['content'];
final previousContent = json.get(['unsigned', 'prev_content']);
return AliasesChangeEvent(
args,
content: AliasesChange.fromJson(content),
previousContent: previousContent != null
? AliasesChange.fromJson(previousContent)
: null,
stateKey: json['state_key'],
);
}
}
class AliasesChange extends EventContent {
final List<RoomAlias> aliases;
AliasesChange({@required this.aliases});
factory AliasesChange.fromJson(Map<String, dynamic> content) {
var aliases = content['aliases'] as List<dynamic>;
var convertedAliases;
......@@ -38,20 +70,10 @@ class AliasesChangeEvent extends StateEvent {
.toList(growable: false);
}
return AliasesChangeEvent(
AliasesChange(
aliases: convertedAliases,
),
args,
stateKey: json['state_key'],
return AliasesChange(
aliases: convertedAliases,
);
}
}
class AliasesChange extends StateEventContent {
final List<RoomAlias> aliases;
AliasesChange({@required this.aliases});
@override
Map<String, dynamic> toJson() => super.toJson()
......@@ -59,3 +81,13 @@ class AliasesChange extends StateEventContent {
'aliases': aliases?.map((a) => a.toString())?.toList(growable: false),
});
}
class AliasesChangeDiff extends Diff {
final AliasesChange _previous;
final AliasesChange _current;
AliasesChangeDiff(this._previous, this._current);
List<Change<RoomAlias>> get aliases =>
_previous.aliases.diff(_current.aliases);
}
......@@ -11,26 +11,20 @@ import 'package:meta/meta.dart';
import '../../event.dart';
class PowerLevelsChangeEvent extends StateEvent {
class PowerLevelsChangeEvent extends StateEvent implements HasDiff {
@override
final PowerLevelsChange content;
@override
final PowerLevelsChange previousContent;
/// Contains information whether certain properties of the
/// [PowerLevelsChangeEvent] have changed compared to the previous
/// [PowerLevelsChangeEvent].
///
/// If it's null, it means the previous content was not provided and what
/// exactly was changed is unknown.
final Changed changed;
@override
final PowerLevelChangeDiff diff;
PowerLevelsChangeEvent(
this.content,
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : changed = previousContent != null
? Changed._(previousContent, content)
: null,
}) : diff = PowerLevelChangeDiff(previousContent, content),
super(args, stateKey: '');
factory PowerLevelsChangeEvent.fromJson(RoomEventArgs args, dynamic json) {
......@@ -42,8 +36,8 @@ class PowerLevelsChangeEvent extends StateEvent {
final previousContent = unsigned != null ? unsigned['prev_content'] : null;
return PowerLevelsChangeEvent(
PowerLevelsChange.fromJson(json['content']),
args,
content: PowerLevelsChange.fromJson(json['content']),
previousContent: previousContent != null
? PowerLevelsChange.fromJson(previousContent)
: null,
......@@ -51,7 +45,7 @@ class PowerLevelsChangeEvent extends StateEvent {
}
}
class PowerLevelsChange extends StateEventContent {
class PowerLevelsChange extends EventContent {
final int banLevel;
final int inviteLevel;
final int kickLevel;
......@@ -173,40 +167,46 @@ class PowerLevelsChange extends StateEventContent {
}
}
class Changed {
class PowerLevelChangeDiff extends Diff {
final PowerLevelsChange _previous;
final PowerLevelsChange _current;
Changed._(this._previous, this._current);
PowerLevelChangeDiff(this._previous, this._current);
bool get banLevel => _previous.banLevel != _current.banLevel;
bool get inviteLevel => _previous.inviteLevel != _current.inviteLevel;
bool get kickLevel => _previous.kickLevel != _current.kickLevel;
bool get redactLevel => _previous.redactLevel != _current.redactLevel;
Change<int> get banLevel => Change(_previous.banLevel, _current.banLevel);
bool get stateEventsDefaultLevel =>
_previous.stateEventsDefaultLevel != _current.stateEventsDefaultLevel;
Change<int> get inviteLevel =>
Change(_previous.inviteLevel, _current.inviteLevel);
bool get eventsDefaultLevel =>
_previous.eventsDefaultLevel != _current.eventsDefaultLevel;
Change<int> get kickLevel => Change(_previous.kickLevel, _current.kickLevel);
bool eventLevel({@required Type of}) {
final previous = _previous.eventLevels[of];
final current = _current.eventLevels[of];
Change<int> get redactLevel =>
Change(_previous.redactLevel, _current.redactLevel);
return previous != current;
}
Change<int> get stateEventsDefaultLevel => Change(
_previous.stateEventsDefaultLevel,
_current.stateEventsDefaultLevel,
);
bool get userDefaultLevel =>
_previous.userDefaultLevel != _current.userDefaultLevel;
Change<int> get eventsDefaultLevel =>
Change(_previous.eventsDefaultLevel, _current.eventsDefaultLevel);
bool userLevel({@required UserId of}) {
final previous = _previous.userLevels[of];
final current = _current.userLevels[of];
Map<Type, Change<int>> get eventLevels => _previous.eventLevels
.diff(_current.eventLevels)
// Handle removals as changes to default levels
.map((key, change) => change is Removed
? MapEntry(key, Edited(change.value, _current.eventsDefaultLevel))
: MapEntry(key, change));
return previous != current;
}
Change<int> get userDefaultLevel =>
Change(_previous.userDefaultLevel, _current.userDefaultLevel);
Map<UserId, Change<int>> get userLevels => _previous.userLevels
.diff(_current.userLevels) // Handle removals as changes to default levels
.map((key, change) => change is Removed
? MapEntry(key, Edited(change.value, _current.userDefaultLevel))
: MapEntry(key, change));
bool get roomNotificationLevel =>
_previous.roomNotificationLevel != _current.roomNotificationLevel;
Change<int> get roomNotificationLevel =>
Change(_previous.roomNotificationLevel, _current.roomNotificationLevel);
}
......@@ -9,40 +9,54 @@ import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:matrix_sdk/src/util/mxc_url.dart';
import 'package:meta/meta.dart';
import '../../event.dart';
import '../../../util/map.dart';
class RoomAvatarChangeEvent extends StateEvent {
@override
final RoomAvatarChange content;
RoomAvatarChangeEvent(this.content, RoomEventArgs args)
: super(args, stateKey: '');
factory RoomAvatarChangeEvent.fromJson(RoomEventArgs args, dynamic json) {
if (!hasStateKey(json)) {
return null;
}
@override
final RoomAvatarChange previousContent;
final url = json['content']['url'];
RoomAvatarChangeEvent(
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : super(args, stateKey: '');
// TODO: Handle avatar removals
if (url == null) {
factory RoomAvatarChangeEvent.fromJson(
RoomEventArgs args, Map<String, dynamic> json) {
if (!hasStateKey(json)) {
return null;
}
final previousContent = json.get(['unsigned', 'prev_content']);
return RoomAvatarChangeEvent(
RoomAvatarChange(
url: tryParseMxcUrl(url),
),
args,
content: RoomAvatarChange.fromJson(json['content']),
previousContent: previousContent != null
? RoomAvatarChange.fromJson(previousContent)
: null,
);
}
}
class RoomAvatarChange extends StateEventContent {
class RoomAvatarChange extends EventContent {
/// An `mxc` url pointing to the avatar.
final Uri url;
RoomAvatarChange({@required this.url});
factory RoomAvatarChange.fromJson(Map<String, dynamic> content) {
var url = content['url'];
if (url != null) {
url = tryParseMxcUrl(url);
}
return RoomAvatarChange(url: url);
}
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
......
......@@ -8,55 +8,62 @@ import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:matrix_sdk/src/event/room/room_event.dart';
import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:matrix_sdk/src/identifier.dart';
import 'package:matrix_sdk/src/util/mxc_url.dart';
import 'package:meta/meta.dart';
import '../../../user.dart';
import '../../../util/map.dart';
class RoomCreationEvent extends StateEvent {
@override
final RoomCreation content;
RoomCreationEvent(this.content, RoomEventArgs args)
: super(args, stateKey: '');
@override
final RoomCreation previousContent;
User get creator => sender;
RoomCreationEvent(
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : super(args, stateKey: '');
static Future<RoomCreationEvent> fromJson(
RoomEventArgs args,
dynamic json, {
Map<String, dynamic> json, {
ProcessContext context,
}) async {
if (!hasStateKey(json)) {
return null;
}
final content = json['content'];
final previousContent = json.get(['unsigned', 'prev_content']);
return RoomCreationEvent(
args,
content: RoomCreation.fromJson(json['content']),
previousContent: previousContent != null
? RoomCreation.fromJson(previousContent)
: null,
);
}
}
final creatorId = UserId(content['creator']);
class RoomCreation extends EventContent {
/// Whether the created room is federated.
final bool federate;
// If the event is from storage, there will be creator state info available
User creator = User(id: creatorId);
final creatorState = content['creator_state'];
if (creatorState != null) {
var avatarUrl = creatorState['avatar_url'];
if (avatarUrl != null) {
avatarUrl = tryParseMxcUrl(avatarUrl);
}
final String roomVersion;
var since = creatorState['since'];
if (since != null) {
since = DateTime.fromMillisecondsSinceEpoch(since);
}
final RoomId previousRoomId;
final EventId previousRoomLastEventId;
creator = User(
id: creatorId,
state: UserState(
displayName: creatorState['display_name'],
avatarUrl: avatarUrl,
since: since,
roomId: args.roomId,
));
}
RoomCreation({
@required this.federate,
@required this.roomVersion,
@required this.previousRoomId,
@required this.previousRoomLastEventId,
});
factory RoomCreation.fromJson(Map<String, dynamic> content) {
final federate = content['m.federate'] ?? true;
final roomVersion = content['room_version'] ?? '1';
......@@ -75,57 +82,22 @@ class RoomCreationEvent extends StateEvent {
}
}
return RoomCreationEvent(
RoomCreation(
creator: creator ?? await context?.members?.getStateOf(creatorId),
federate: federate,
roomVersion: roomVersion,
previousRoomId: previousRoomId,
previousRoomLastEventId: previousRoomEventId,
),
args,
return RoomCreation(
federate: federate,
roomVersion: roomVersion,
previousRoomId: previousRoomId,
previousRoomLastEventId: previousRoomEventId,
);
}
}
class RoomCreation extends StateEventContent {
/// User id of the creator of the room.
///
/// Use [room.creator] to get the state of the
/// creator at time of creation.
final User creator;
/// Whether the created room is federated.
final bool federate;
final String roomVersion;
final RoomId previousRoomId;
final EventId previousRoomLastEventId;
RoomCreation({
@required this.creator,
@required this.federate,
@required this.roomVersion,
@required this.previousRoomId,
@required this.previousRoomLastEventId,
});
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
'm.federate': federate,
'room_version': roomVersion,
'creator': creator.id,
'predecessor': {
'room_id': previousRoomId?.toString(),
'event_id': previousRoomLastEventId?.toString(),
},
// Not a official field in the spec
'creator_state': {
'display_name': creator.name,
'avatar_url': creator.avatarUrl?.toString(),
'since': creator.state?.since?.millisecondsSinceEpoch,
}
});
}
......@@ -8,31 +8,48 @@ import 'package:matrix_sdk/src/event/room/room_event.dart';
import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:meta/meta.dart';
import '../../event.dart';
import '../../../util/map.dart';
class RoomNameChangeEvent extends StateEvent {
@override
final RoomNameChange content;
RoomNameChangeEvent(this.content, RoomEventArgs args)
: super(args, stateKey: '');
@override
final RoomNameChange previousContent;
RoomNameChangeEvent(
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : super(args, stateKey: '');
factory RoomNameChangeEvent.fromJson(RoomEventArgs args, dynamic json) {
factory RoomNameChangeEvent.fromJson(
RoomEventArgs args, Map<String, dynamic> json) {
if (!hasStateKey(json)) {
return null;
}
var name = json['content']['name'];
final previousContent = json.get(['unsigned', 'prev_content']);
return RoomNameChangeEvent(
RoomNameChange(name: name),
args,
content: RoomNameChange.fromJson(json['content']),
previousContent: previousContent != null
? RoomNameChange.fromJson(previousContent)
: null,
);
}
}
class RoomNameChange extends StateEventContent {
class RoomNameChange extends EventContent {
final String name;
RoomNameChange({@required this.name});
factory RoomNameChange.fromJson(Map<String, dynamic> content) {
return RoomNameChange(name: content['name']);
}
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
......
......@@ -9,39 +9,43 @@ import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:matrix_sdk/src/identifier.dart';
import 'package:meta/meta.dart';
import '../../event.dart';
import '../../../util/map.dart';
class RoomUpgradeEvent extends StateEvent {
@override
final RoomUpgrade content;
RoomUpgradeEvent(this.content, RoomEventArgs args)
: super(args, stateKey: '');
factory RoomUpgradeEvent.fromJson(RoomEventArgs args, dynamic json) {
if (!hasStateKey(json)) {
return null;
}
@override
final RoomUpgrade previousContent;
final content = json['content'];
RoomUpgradeEvent(
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : super(args, stateKey: '');
final body = content['body'];
var replacementRoomId = content['replacement_room'];
if (replacementRoomId == null) {
factory RoomUpgradeEvent.fromJson(
RoomEventArgs args,
Map<String, dynamic> json,
) {
if (!hasStateKey(json)) {
return null;
}
replacementRoomId = RoomId(replacementRoomId);
final previousContent = json.get(['unsigned', 'prev_content']);
return RoomUpgradeEvent(
RoomUpgrade(
body: body,
replacementRoomId: replacementRoomId,
),
args,
content: RoomUpgrade.fromJson(json['content']),
previousContent: previousContent != null
? RoomUpgrade.fromJson(previousContent)
: null,
);
}
}
class RoomUpgrade extends StateEventContent {
class RoomUpgrade extends EventContent {
final String body;
final RoomId replacementRoomId;
......@@ -51,6 +55,21 @@ class RoomUpgrade extends StateEventContent {
@required this.replacementRoomId,
});
factory RoomUpgrade.fromJson(Map<String, dynamic> content) {
final body = content['body'];
var replacementRoomId = content['replacement_room'];
if (replacementRoomId == null) {
return null;
}
replacementRoomId = RoomId(replacementRoomId);
return RoomUpgrade(
body: body,
replacementRoomId: replacementRoomId,
);
}
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
......
......@@ -4,24 +4,125 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import 'package:matrix_sdk/src/event/event.dart';
import 'package:matrix_sdk/src/event/room/room_event.dart';
import '../../event.dart';
abstract class StateEvent extends RoomEvent {
@override
StateEventContent get content;
EventContent get previousContent;
final String stateKey;
StateEvent(RoomEventArgs args, {this.stateKey = ''}) : super(args);
@override
Map<String, dynamic> toJson() =>
super.toJson()..addAll({'state_key': stateKey});
Map<String, dynamic> toJson() => super.toJson()
..addAll({'state_key': stateKey})
..['unsigned'].addAll({
'prev_content': previousContent?.toJson(),
});
}
/// Marker interface for all diffs for a [StateEvent]s
/// [StateEvent.previousContent] and [Event.content].
///
/// A [Diff] should implement the [EventContent] of the respective
/// [StateEvent], and the properties should be `null` if there is no change,
/// or the [Event.content]'s value if there was a change.
///
/// If a property is a [Map], it will contain the keys of the previous and
/// current content combined, with values which are null if they're unchanged,
/// and the current values if they've changed.
abstract class Diff {}
abstract class HasDiff {
Diff get diff;
}
abstract class Change<T> {
T get value;
Change._();
factory Change(T previous, T current, {bool inMap = false}) {
if (!inMap) {
return previous != current
? Edited(previous, current)
: Unchanged(current);
} else {
if (previous == current) {
return Unchanged(current);
} else if (previous == null && current != null) {
return Added(current);
} else if (previous != null && current == null) {
return Removed(previous);
} else {
return Edited(previous, current);
}
}
}
@override
String toString() => '$runtimeType($value)';
}
extension DiffMapExtension<K, V> on Map<K, V> {
Map<K, Change<V>> diff(Map<K, V> current) => Map.fromEntries(
this.keys.followedBy(current.keys).toSet().map(
(key) => MapEntry(
key,
Change(this[key], current[key], inMap: true),
),
),
);
}
extension DiffListExtension<T> on List<T> {
List<Change<T>> diff(List<T> current) {
final removals =
this.where((e) => !current.contains(e)).map((e) => Removed(e));
final additions =
current.where((e) => !this.contains(e)).map((e) => Added(e));
final unchanged =
this.where((e) => current.contains(e)).map((e) => Unchanged(e));
return [...unchanged, ...additions, ...removals];
}
}
abstract class StateEventContent extends EventContent {
final String stateKey = '';
class Unchanged<T> extends Change<T> {
@override
final T value;
Unchanged(this.value) : super._();
}
class Added<T> extends Change<T> {
@override
final T value;
Added(this.value) : super._();
}
class Removed<T> extends Change<T> {
@override
final T value;
Removed(this.value) : super._();
}
class Edited<T> extends Change<T> {
final T previousValue;
@override
final T value;
Edited(this.previousValue, this.value) : super._();
@override
String toString() => '$runtimeType($previousValue -> $value)';
}
/// @internal
......
......@@ -8,35 +8,50 @@ import 'package:matrix_sdk/src/event/room/room_event.dart';
import 'package:matrix_sdk/src/event/room/state/state_event.dart';
import 'package:meta/meta.dart';
import '../../event.dart';
import '../../../util/map.dart';
class TopicChangeEvent extends StateEvent {
@override
final TopicChange content;
TopicChangeEvent(this.content, RoomEventArgs args)
: super(args, stateKey: '');
factory TopicChangeEvent.fromJson(RoomEventArgs args, dynamic json) {
@override
final TopicChange previousContent;
TopicChangeEvent(
RoomEventArgs args, {
@required this.content,
this.previousContent,
}) : super(args, stateKey: '');
factory TopicChangeEvent.fromJson(
RoomEventArgs args,
Map<String, dynamic> json,
) {
if (!hasStateKey(json)) {
return null;
}
final content = json['content'];
final body = content['topic'];
final previousContent = json.get(['unsigned', 'prev_content']);
return TopicChangeEvent(
TopicChange(
topic: body,
),
args,
content: TopicChange.fromJson(json['content']),
previousContent: previousContent != null
? TopicChange.fromJson(previousContent)
: null,
);
}
}
class TopicChange extends StateEventContent {
class TopicChange extends EventContent {
final String topic;
TopicChange({@required this.topic});
factory TopicChange.fromJson(Map<String, dynamic> content) {
return TopicChange(topic: content['topic']);
}
@override
Map<String, dynamic> toJson() => super.toJson()
..addAll({
......
......@@ -72,7 +72,7 @@ class Timeline extends Storables<EventId, RoomEvent> {
Future<void> addAll(Iterable<RoomEvent> values) async {
// Remove echo events
for (final event in values) {
final transactionId = event?.unsigned?.transactionId;
final transactionId = event?.transactionId;
if (transactionId != null) {
// delete local echo
await remove(EventId(transactionId));
......
......@@ -60,8 +60,8 @@ class Room extends Identifiable<RoomId> {
bool get isDirect => directUser != null;
/// [UserId] of the user that created this room.
User get creator => stateEvents.creation?.content?.creator;
/// [User] that created this room.
User get creator => stateEvents.creation?.creator;
/// The [RoomId] of the room that replaced this room.
RoomId get replacementId => stateEvents.upgrade?.content?.replacementRoomId;
......@@ -133,7 +133,7 @@ class Room extends Identifiable<RoomId> {
_members = Members(this, memberCount, scope);
_timeline = Timeline(this, scope);
_client = Container().resolve(_scope);
_powerLevels = PowerLevels(_stateEvents.powerLevelsChange);
_powerLevels = PowerLevels._(_stateEvents.powerLevelsChange);
}
User _directUser;
......@@ -332,7 +332,7 @@ class Room extends Identifiable<RoomId> {
} else if (event is PowerLevelsChangeEvent &&
isNewer(event, stateEvents.powerLevelsChange)) {
_stateEvents = stateEvents.copyWith(powerLevelsChangeEvent: event);
_powerLevels = PowerLevels(stateEvents.powerLevelsChange);
_powerLevels = PowerLevels._(stateEvents.powerLevelsChange);
} else if (event is RoomCreationEvent) {
_stateEvents = stateEvents.copyWith(creationEvent: event);
} else if (event is RoomUpgradeEvent &&
......@@ -345,8 +345,8 @@ class Room extends Identifiable<RoomId> {
// Check whether there are only two people in the
// room
if (members.count <= 2) {
if (event.content.subject != _me) {
_directUser = event.content.subject;
if (event.subject != _me) {
_directUser = event.subject;
} else if (event.sender != _me) {
_directUser = event.sender;
}
......@@ -354,7 +354,7 @@ class Room extends Identifiable<RoomId> {
} else if (event is JoinEvent && this.isDirect) {
// If someone joins who is not me or the direct user,
// this room is no longer direct
final subject = event.content.subject;
final subject = event.subject;
if (subject != _me && subject != directUser) {
_directUser = null;
}
......@@ -566,8 +566,8 @@ class JoinedRoom extends Room {
sender: User(
id: Container().resolve<LocalUser>(_scope).id,
),
unsigned: UnsignedData(transactionId: transactionId),
sentState: SentState.unsent,
transactionId: transactionId,
);
final event = Event.fromContent(content, eventArgs);
......@@ -792,7 +792,7 @@ class PowerLevels {
final PowerLevelsChange _content;
PowerLevels(PowerLevelsChangeEvent event)
PowerLevels._(PowerLevelsChangeEvent event)
: _content = event?.content,
defaults = PowerLevelDefaults(event);
......
......@@ -281,7 +281,7 @@ class LocalUser extends User {
for (RoomEvent event in await _store.getUnsentEvents(room)) {
await for (final _ in room.send(
event.content,
transactionId: event.unsigned?.transactionId,
transactionId: event.transactionId,
)) {}
}
}
......
// Copyright (C) 2019 Wilko Manger
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
extension MapExtensions on Map {
/// Safe way to get a something from a nested map.
///
/// Will return null if any of the keys don't exist or are not a Map.
dynamic get(List keys) {
var current;
for (final key in keys) {
current = this[key];
if (current == null || current is! Map) {
return current;
}
}
return current;
}
}