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