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:
Krille 2024-05-03 09:22:16 +02:00
parent ffe496a234
commit 5b46ae6e31
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
6 changed files with 69 additions and 43 deletions

View File

@ -1312,7 +1312,10 @@ class Client extends MatrixApi {
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
/// an error?
@ -2385,7 +2388,7 @@ class Client extends MatrixApi {
switch (eventUpdate.type) {
case EventUpdateType.inviteState:
room.setState(Event.fromJson(eventUpdate.content, room));
room.setState(StrippedStateEvent.fromJson(eventUpdate.content));
break;
case EventUpdateType.state:
case EventUpdateType.timeline:

View File

@ -247,10 +247,7 @@ class Event extends MatrixEvent {
prevContent: prevContent,
content: content,
typeKey: type,
eventId: eventId,
senderId: senderId,
originServerTs: originServerTs,
unsigned: unsigned,
room: room,
);

View File

@ -94,7 +94,7 @@ class Room {
/// 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
/// 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.
Map<String, BasicRoomEvent> ephemerals = {};
@ -157,19 +157,31 @@ class Room {
/// Returns the [Event] for the given [typeKey] and optional [stateKey].
/// 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];
/// Adds the [state] to this room and overwrites a state with the same
/// typeKey/stateKey key pair if there is one.
void setState(Event state) {
void setState(StrippedStateEvent state) {
// Ignore other non-state events
final stateKey = state.stateKey;
final roomId = state.roomId;
if (roomId == null || roomId != id) {
Logs().w('Tried to set state event for wrong room!');
return;
// For non invite rooms this is usually an Event and we should validate
// the room ID:
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) {
Logs().w(
'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;
client.onRoomState.add(state);
client.onRoomState.add((roomId: id, state: state));
}
/// ID of the fully read marker event.
@ -276,9 +288,11 @@ class Room {
if (membership == Membership.invite) {
final ownMember = unsafeGetUserFromMemoryOrFallback(client.userID!);
ownMember.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
unsafeGetUserFromMemoryOrFallback(ownMember.senderId)
.calcDisplayname(i18n: i18n);
if (ownMember.senderId != ownMember.stateKey) {
return ownMember.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n);
return unsafeGetUserFromMemoryOrFallback(ownMember.senderId)
.calcDisplayname(i18n: i18n);
}
}
if (membership == Membership.leave) {
@ -312,7 +326,7 @@ class Room {
if (heroes != null && heroes.length == 1) {
final hero = getState(EventTypes.RoomMember, heroes.first);
if (hero != null) {
return hero.asUser.avatarUrl;
return hero.asUser(this).avatarUrl;
}
}
if (isDirectChat) {
@ -383,6 +397,7 @@ class Room {
states.forEach((final String key, final entry) {
final state = entry[''];
if (state == null) return;
if (state is! Event) return;
if (state.originServerTs.millisecondsSinceEpoch >
lastTime.millisecondsSinceEpoch) {
lastTime = state.originServerTs;
@ -1554,7 +1569,7 @@ class Room {
if (members != null) {
return members.entries
.where((entry) => entry.value.type == EventTypes.RoomMember)
.map((entry) => entry.value.asUser)
.map((entry) => entry.value.asUser(this))
.where((user) => membershipFilter.contains(user.membership))
.toList();
}
@ -1643,7 +1658,7 @@ class Room {
User unsafeGetUserFromMemoryOrFallback(String mxID) {
final user = getState(EventTypes.RoomMember, mxID);
if (user != null) {
return user.asUser;
return user.asUser(this);
} else {
if (mxID.isValidMatrixId) {
// ignore: discarded_futures
@ -1672,7 +1687,7 @@ class Room {
// Checks if the user is really missing
final stateUser = getState(EventTypes.RoomMember, mxID);
if (stateUser != null) {
return stateUser.asUser;
return stateUser.asUser(this);
}
// it may be in the database

View File

@ -19,7 +19,10 @@
import 'package:matrix/matrix.dart';
/// 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(
String id, {
String? membership,
@ -30,7 +33,6 @@ class User extends Event {
return User.fromState(
stateKey: id,
senderId: id,
eventId: 'fake_event',
content: {
if (membership != null) 'membership': membership,
if (displayName != null) 'displayname': displayName,
@ -38,20 +40,16 @@ class User extends Event {
},
typeKey: EventTypes.RoomMember,
room: room,
originServerTs: DateTime.now(),
);
}
User.fromState({
super.prevContent,
required String super.stateKey,
super.content = const {},
required String typeKey,
required super.eventId,
required super.senderId,
required super.originServerTs,
super.unsigned,
required super.room,
required this.room,
this.prevContent,
}) : super(
type: typeKey,
);
@ -241,3 +239,14 @@ class User extends Event {
const _maximumHashLength = 10000;
String _hash(String s) =>
(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,
);
}

View File

@ -17,7 +17,6 @@
*/
import 'package:matrix/matrix_api_lite.dart';
import 'package:matrix/src/event.dart';
class SpaceChild {
final String? roomId;
@ -25,7 +24,7 @@ class SpaceChild {
final String order;
final bool? suggested;
SpaceChild.fromState(Event state)
SpaceChild.fromState(StrippedStateEvent state)
: assert(state.type == EventTypes.SpaceChild),
roomId = state.stateKey,
via = state.content.tryGetList<String>('via') ?? [],
@ -38,7 +37,7 @@ class SpaceParent {
final List<String> via;
final bool? canonical;
SpaceParent.fromState(Event state)
SpaceParent.fromState(StrippedStateEvent state)
: assert(state.type == EventTypes.SpaceParent),
roomId = state.stateKey,
via = state.content.tryGetList<String>('via') ?? [],

View File

@ -89,19 +89,22 @@ class VoIP {
// handles the com.famedly.call events.
client.onRoomState.stream.listen(
(event) async {
if (event.type == EventTypes.GroupCallMember) {
Logs().v('[VOIP] onRoomState: type ${event.toJson()}');
final mems = event.room.getCallMembershipsFromEvent(event);
for (final mem in mems) {
unawaited(createGroupCallFromRoomStateEvent(mem));
}
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();
}
(update) async {
final event = update.state;
if (event is! Event) return;
if (event.room.membership != Membership.join) return;
if (event.type != EventTypes.GroupCallMember) return;
Logs().v('[VOIP] onRoomState: type ${event.toJson()}');
final mems = event.room.getCallMembershipsFromEvent(event);
for (final mem in mems) {
unawaited(createGroupCallFromRoomStateEvent(mem));
}
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();
}
}
},