Merge pull request #1786 from famedly/use-stripped-state-for-room-state

refactor: Use strippedstatevent as base for room state and user class
This commit is contained in:
Krille-chan 2024-05-03 14:32:21 +02:00 committed by GitHub
commit 587ce378c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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> 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:

View File

@ -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,
); );

View File

@ -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

View File

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

View File

@ -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') ?? [],

View File

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