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(); } } },