From 5b46ae6e318dc658be7e8e8de70fb029f84901ff Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 3 May 2024 09:22:16 +0200 Subject: [PATCH] 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. --- lib/src/client.dart | 7 ++++-- lib/src/event.dart | 3 --- lib/src/room.dart | 43 +++++++++++++++++++++++----------- lib/src/user.dart | 25 +++++++++++++------- lib/src/utils/space_child.dart | 5 ++-- lib/src/voip/voip.dart | 29 +++++++++++++---------- 6 files changed, 69 insertions(+), 43 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 0c979ff5..ae77f37b 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1312,7 +1312,10 @@ class Client extends MatrixApi { final CachedStreamController onGroupMember = CachedStreamController(); - final CachedStreamController 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: diff --git a/lib/src/event.dart b/lib/src/event.dart index 8ba61892..e8f22709 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -247,10 +247,7 @@ class Event extends MatrixEvent { prevContent: prevContent, content: content, typeKey: type, - eventId: eventId, senderId: senderId, - originServerTs: originServerTs, - unsigned: unsigned, room: room, ); diff --git a/lib/src/room.dart b/lib/src/room.dart index 6763655b..e0962762 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -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> states = {}; + Map> states = {}; /// Key-Value store for ephemerals. Map 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 diff --git a/lib/src/user.dart b/lib/src/user.dart index 03b3881e..82b780a2 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -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? 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(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, + ); +} diff --git a/lib/src/utils/space_child.dart b/lib/src/utils/space_child.dart index d6cdc056..a4bd6240 100644 --- a/lib/src/utils/space_child.dart +++ b/lib/src/utils/space_child.dart @@ -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('via') ?? [], @@ -38,7 +37,7 @@ class SpaceParent { final List via; final bool? canonical; - SpaceParent.fromState(Event state) + SpaceParent.fromState(StrippedStateEvent state) : assert(state.type == EventTypes.SpaceParent), roomId = state.stateKey, via = state.content.tryGetList('via') ?? [], diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index 2df35369..6d381a7a 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -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(); } } },