refactor: Use strippedstatevent as base for room state and user class
Before we have used the Event class for all state events while for invite rooms those actually were StrippedStateEvent objects. This created some problems like we needed to set a fake originServerTs (usually to DateTime.now()). Actually we don't need the additional keys for state events most of the time so just using StrippedStateEvent for all states and typecasting them to event where needed is not much of a hassle while we benefit from a more clear structure. This also now uses StrippedStateEvent as a base class for the User class which makes the User class more minimal as keys like event_id and origin_server_ts are no longer necessary. As we create a lot of fake User objects where we had to put fake values in it, it brings more benefits than problems to just get rid of those fields.
This commit is contained in:
parent
ffe496a234
commit
5b46ae6e31
|
|
@ -1312,7 +1312,10 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
final CachedStreamController<Event> onGroupMember = CachedStreamController();
|
final CachedStreamController<Event> onGroupMember = CachedStreamController();
|
||||||
|
|
||||||
final CachedStreamController<Event> onRoomState = CachedStreamController();
|
/// When a state in a room has been updated this will return the room ID
|
||||||
|
/// and the state event.
|
||||||
|
final CachedStreamController<({String roomId, StrippedStateEvent state})>
|
||||||
|
onRoomState = CachedStreamController();
|
||||||
|
|
||||||
/// How long should the app wait until it retrys the synchronisation after
|
/// How long should the app wait until it retrys the synchronisation after
|
||||||
/// an error?
|
/// an error?
|
||||||
|
|
@ -2385,7 +2388,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
switch (eventUpdate.type) {
|
switch (eventUpdate.type) {
|
||||||
case EventUpdateType.inviteState:
|
case EventUpdateType.inviteState:
|
||||||
room.setState(Event.fromJson(eventUpdate.content, room));
|
room.setState(StrippedStateEvent.fromJson(eventUpdate.content));
|
||||||
break;
|
break;
|
||||||
case EventUpdateType.state:
|
case EventUpdateType.state:
|
||||||
case EventUpdateType.timeline:
|
case EventUpdateType.timeline:
|
||||||
|
|
|
||||||
|
|
@ -247,10 +247,7 @@ class Event extends MatrixEvent {
|
||||||
prevContent: prevContent,
|
prevContent: prevContent,
|
||||||
content: content,
|
content: content,
|
||||||
typeKey: type,
|
typeKey: type,
|
||||||
eventId: eventId,
|
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
originServerTs: originServerTs,
|
|
||||||
unsigned: unsigned,
|
|
||||||
room: room,
|
room: room,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class Room {
|
||||||
/// The room states are a key value store of the key (`type`,`state_key`) => State(event).
|
/// The room states are a key value store of the key (`type`,`state_key`) => State(event).
|
||||||
/// In a lot of cases the `state_key` might be an empty string. You **should** use the
|
/// In a lot of cases the `state_key` might be an empty string. You **should** use the
|
||||||
/// methods `getState()` and `setState()` to interact with the room states.
|
/// methods `getState()` and `setState()` to interact with the room states.
|
||||||
Map<String, Map<String, Event>> states = {};
|
Map<String, Map<String, StrippedStateEvent>> states = {};
|
||||||
|
|
||||||
/// Key-Value store for ephemerals.
|
/// Key-Value store for ephemerals.
|
||||||
Map<String, BasicRoomEvent> ephemerals = {};
|
Map<String, BasicRoomEvent> ephemerals = {};
|
||||||
|
|
@ -157,19 +157,31 @@ class Room {
|
||||||
|
|
||||||
/// Returns the [Event] for the given [typeKey] and optional [stateKey].
|
/// Returns the [Event] for the given [typeKey] and optional [stateKey].
|
||||||
/// If no [stateKey] is provided, it defaults to an empty string.
|
/// If no [stateKey] is provided, it defaults to an empty string.
|
||||||
Event? getState(String typeKey, [String stateKey = '']) =>
|
/// This returns either a `StrippedStateEvent` for rooms with membership
|
||||||
|
/// "invite" or a `User`/`Event`. If you need additional information like
|
||||||
|
/// the Event ID or originServerTs you need to do a type check like:
|
||||||
|
/// ```dart
|
||||||
|
/// if (state is Event) { /*...*/ }
|
||||||
|
/// ```
|
||||||
|
StrippedStateEvent? getState(String typeKey, [String stateKey = '']) =>
|
||||||
states[typeKey]?[stateKey];
|
states[typeKey]?[stateKey];
|
||||||
|
|
||||||
/// Adds the [state] to this room and overwrites a state with the same
|
/// Adds the [state] to this room and overwrites a state with the same
|
||||||
/// typeKey/stateKey key pair if there is one.
|
/// typeKey/stateKey key pair if there is one.
|
||||||
void setState(Event state) {
|
void setState(StrippedStateEvent state) {
|
||||||
// Ignore other non-state events
|
// Ignore other non-state events
|
||||||
final stateKey = state.stateKey;
|
final stateKey = state.stateKey;
|
||||||
final roomId = state.roomId;
|
|
||||||
if (roomId == null || roomId != id) {
|
// For non invite rooms this is usually an Event and we should validate
|
||||||
Logs().w('Tried to set state event for wrong room!');
|
// the room ID:
|
||||||
return;
|
if (state is Event) {
|
||||||
|
final roomId = state.roomId;
|
||||||
|
if (roomId == null || roomId != id) {
|
||||||
|
Logs().wtf('Tried to set state event for wrong room!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stateKey == null) {
|
if (stateKey == null) {
|
||||||
Logs().w(
|
Logs().w(
|
||||||
'Tried to set a non state event with type "${state.type}" as state event for a room',
|
'Tried to set a non state event with type "${state.type}" as state event for a room',
|
||||||
|
|
@ -179,7 +191,7 @@ class Room {
|
||||||
|
|
||||||
(states[state.type] ??= {})[stateKey] = state;
|
(states[state.type] ??= {})[stateKey] = state;
|
||||||
|
|
||||||
client.onRoomState.add(state);
|
client.onRoomState.add((roomId: id, state: state));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ID of the fully read marker event.
|
/// ID of the fully read marker event.
|
||||||
|
|
@ -276,9 +288,11 @@ class Room {
|
||||||
if (membership == Membership.invite) {
|
if (membership == Membership.invite) {
|
||||||
final ownMember = unsafeGetUserFromMemoryOrFallback(client.userID!);
|
final ownMember = unsafeGetUserFromMemoryOrFallback(client.userID!);
|
||||||
|
|
||||||
ownMember.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
|
unsafeGetUserFromMemoryOrFallback(ownMember.senderId)
|
||||||
|
.calcDisplayname(i18n: i18n);
|
||||||
if (ownMember.senderId != ownMember.stateKey) {
|
if (ownMember.senderId != ownMember.stateKey) {
|
||||||
return ownMember.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
|
return unsafeGetUserFromMemoryOrFallback(ownMember.senderId)
|
||||||
|
.calcDisplayname(i18n: i18n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (membership == Membership.leave) {
|
if (membership == Membership.leave) {
|
||||||
|
|
@ -312,7 +326,7 @@ class Room {
|
||||||
if (heroes != null && heroes.length == 1) {
|
if (heroes != null && heroes.length == 1) {
|
||||||
final hero = getState(EventTypes.RoomMember, heroes.first);
|
final hero = getState(EventTypes.RoomMember, heroes.first);
|
||||||
if (hero != null) {
|
if (hero != null) {
|
||||||
return hero.asUser.avatarUrl;
|
return hero.asUser(this).avatarUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isDirectChat) {
|
if (isDirectChat) {
|
||||||
|
|
@ -383,6 +397,7 @@ class Room {
|
||||||
states.forEach((final String key, final entry) {
|
states.forEach((final String key, final entry) {
|
||||||
final state = entry[''];
|
final state = entry[''];
|
||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
|
if (state is! Event) return;
|
||||||
if (state.originServerTs.millisecondsSinceEpoch >
|
if (state.originServerTs.millisecondsSinceEpoch >
|
||||||
lastTime.millisecondsSinceEpoch) {
|
lastTime.millisecondsSinceEpoch) {
|
||||||
lastTime = state.originServerTs;
|
lastTime = state.originServerTs;
|
||||||
|
|
@ -1554,7 +1569,7 @@ class Room {
|
||||||
if (members != null) {
|
if (members != null) {
|
||||||
return members.entries
|
return members.entries
|
||||||
.where((entry) => entry.value.type == EventTypes.RoomMember)
|
.where((entry) => entry.value.type == EventTypes.RoomMember)
|
||||||
.map((entry) => entry.value.asUser)
|
.map((entry) => entry.value.asUser(this))
|
||||||
.where((user) => membershipFilter.contains(user.membership))
|
.where((user) => membershipFilter.contains(user.membership))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
@ -1643,7 +1658,7 @@ class Room {
|
||||||
User unsafeGetUserFromMemoryOrFallback(String mxID) {
|
User unsafeGetUserFromMemoryOrFallback(String mxID) {
|
||||||
final user = getState(EventTypes.RoomMember, mxID);
|
final user = getState(EventTypes.RoomMember, mxID);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
return user.asUser;
|
return user.asUser(this);
|
||||||
} else {
|
} else {
|
||||||
if (mxID.isValidMatrixId) {
|
if (mxID.isValidMatrixId) {
|
||||||
// ignore: discarded_futures
|
// ignore: discarded_futures
|
||||||
|
|
@ -1672,7 +1687,7 @@ class Room {
|
||||||
// Checks if the user is really missing
|
// Checks if the user is really missing
|
||||||
final stateUser = getState(EventTypes.RoomMember, mxID);
|
final stateUser = getState(EventTypes.RoomMember, mxID);
|
||||||
if (stateUser != null) {
|
if (stateUser != null) {
|
||||||
return stateUser.asUser;
|
return stateUser.asUser(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// it may be in the database
|
// it may be in the database
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||||
class User extends Event {
|
class User extends StrippedStateEvent {
|
||||||
|
final Room room;
|
||||||
|
final Map<String, Object?>? prevContent;
|
||||||
|
|
||||||
factory User(
|
factory User(
|
||||||
String id, {
|
String id, {
|
||||||
String? membership,
|
String? membership,
|
||||||
|
|
@ -30,7 +33,6 @@ class User extends Event {
|
||||||
return User.fromState(
|
return User.fromState(
|
||||||
stateKey: id,
|
stateKey: id,
|
||||||
senderId: id,
|
senderId: id,
|
||||||
eventId: 'fake_event',
|
|
||||||
content: {
|
content: {
|
||||||
if (membership != null) 'membership': membership,
|
if (membership != null) 'membership': membership,
|
||||||
if (displayName != null) 'displayname': displayName,
|
if (displayName != null) 'displayname': displayName,
|
||||||
|
|
@ -38,20 +40,16 @@ class User extends Event {
|
||||||
},
|
},
|
||||||
typeKey: EventTypes.RoomMember,
|
typeKey: EventTypes.RoomMember,
|
||||||
room: room,
|
room: room,
|
||||||
originServerTs: DateTime.now(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.fromState({
|
User.fromState({
|
||||||
super.prevContent,
|
|
||||||
required String super.stateKey,
|
required String super.stateKey,
|
||||||
super.content = const {},
|
super.content = const {},
|
||||||
required String typeKey,
|
required String typeKey,
|
||||||
required super.eventId,
|
|
||||||
required super.senderId,
|
required super.senderId,
|
||||||
required super.originServerTs,
|
required this.room,
|
||||||
super.unsigned,
|
this.prevContent,
|
||||||
required super.room,
|
|
||||||
}) : super(
|
}) : super(
|
||||||
type: typeKey,
|
type: typeKey,
|
||||||
);
|
);
|
||||||
|
|
@ -241,3 +239,14 @@ class User extends Event {
|
||||||
const _maximumHashLength = 10000;
|
const _maximumHashLength = 10000;
|
||||||
String _hash(String s) =>
|
String _hash(String s) =>
|
||||||
(s.codeUnits.fold<int>(0, (a, b) => a + b) % _maximumHashLength).toString();
|
(s.codeUnits.fold<int>(0, (a, b) => a + b) % _maximumHashLength).toString();
|
||||||
|
|
||||||
|
extension FromStrippedStateEventExtension on StrippedStateEvent {
|
||||||
|
User asUser(Room room) => User.fromState(
|
||||||
|
// state key should always be set for member events
|
||||||
|
stateKey: stateKey!,
|
||||||
|
content: content,
|
||||||
|
typeKey: type,
|
||||||
|
senderId: senderId,
|
||||||
|
room: room,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:matrix/matrix_api_lite.dart';
|
import 'package:matrix/matrix_api_lite.dart';
|
||||||
import 'package:matrix/src/event.dart';
|
|
||||||
|
|
||||||
class SpaceChild {
|
class SpaceChild {
|
||||||
final String? roomId;
|
final String? roomId;
|
||||||
|
|
@ -25,7 +24,7 @@ class SpaceChild {
|
||||||
final String order;
|
final String order;
|
||||||
final bool? suggested;
|
final bool? suggested;
|
||||||
|
|
||||||
SpaceChild.fromState(Event state)
|
SpaceChild.fromState(StrippedStateEvent state)
|
||||||
: assert(state.type == EventTypes.SpaceChild),
|
: assert(state.type == EventTypes.SpaceChild),
|
||||||
roomId = state.stateKey,
|
roomId = state.stateKey,
|
||||||
via = state.content.tryGetList<String>('via') ?? [],
|
via = state.content.tryGetList<String>('via') ?? [],
|
||||||
|
|
@ -38,7 +37,7 @@ class SpaceParent {
|
||||||
final List<String> via;
|
final List<String> via;
|
||||||
final bool? canonical;
|
final bool? canonical;
|
||||||
|
|
||||||
SpaceParent.fromState(Event state)
|
SpaceParent.fromState(StrippedStateEvent state)
|
||||||
: assert(state.type == EventTypes.SpaceParent),
|
: assert(state.type == EventTypes.SpaceParent),
|
||||||
roomId = state.stateKey,
|
roomId = state.stateKey,
|
||||||
via = state.content.tryGetList<String>('via') ?? [],
|
via = state.content.tryGetList<String>('via') ?? [],
|
||||||
|
|
|
||||||
|
|
@ -89,19 +89,22 @@ class VoIP {
|
||||||
|
|
||||||
// handles the com.famedly.call events.
|
// handles the com.famedly.call events.
|
||||||
client.onRoomState.stream.listen(
|
client.onRoomState.stream.listen(
|
||||||
(event) async {
|
(update) async {
|
||||||
if (event.type == EventTypes.GroupCallMember) {
|
final event = update.state;
|
||||||
Logs().v('[VOIP] onRoomState: type ${event.toJson()}');
|
if (event is! Event) return;
|
||||||
final mems = event.room.getCallMembershipsFromEvent(event);
|
if (event.room.membership != Membership.join) return;
|
||||||
for (final mem in mems) {
|
if (event.type != EventTypes.GroupCallMember) return;
|
||||||
unawaited(createGroupCallFromRoomStateEvent(mem));
|
|
||||||
}
|
Logs().v('[VOIP] onRoomState: type ${event.toJson()}');
|
||||||
for (final map in groupCalls.entries) {
|
final mems = event.room.getCallMembershipsFromEvent(event);
|
||||||
if (map.key.roomId == event.room.id) {
|
for (final mem in mems) {
|
||||||
// because we don't know which call got updated, just update all
|
unawaited(createGroupCallFromRoomStateEvent(mem));
|
||||||
// group calls we have entered for that room
|
}
|
||||||
await map.value.onMemberStateChanged();
|
for (final map in groupCalls.entries) {
|
||||||
}
|
if (map.key.roomId == event.room.id) {
|
||||||
|
// because we don't know which call got updated, just update all
|
||||||
|
// group calls we have entered for that room
|
||||||
|
await map.value.onMemberStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue