add GomuksEvent model

This commit is contained in:
Henry Hiles 2026-11-03 15:16:38 -05:00
commit b154b4ee2e
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
8 changed files with 114 additions and 93 deletions

View file

@ -8,6 +8,7 @@ import "package:nexus/controllers/client.dart";
import "package:nexus/models/account_data.dart"; import "package:nexus/models/account_data.dart";
import "package:nexus/models/push_rules.dart"; import "package:nexus/models/push_rules.dart";
import "package:nexus/models/requests/update_push_rule.dart"; import "package:nexus/models/requests/update_push_rule.dart";
import "package:nexus/models/sync_data.dart";
class AccountDataController extends Notifier<AccountData> { class AccountDataController extends Notifier<AccountData> {
@override @override
@ -26,6 +27,11 @@ class AccountDataController extends Notifier<AccountData> {
} }
}); });
void handleSync(SyncData sync) {
if (sync.clearState) state = .new();
update(sync.accountData);
}
Future<void> setEnabledImagePacks(ImagePackRooms packs) { Future<void> setEnabledImagePacks(ImagePackRooms packs) {
ISet<(String, String)> keysOf(ImagePackRooms rooms) => rooms.rooms.entries ISet<(String, String)> keysOf(ImagePackRooms rooms) => rooms.rooms.entries
.expand((entry) => entry.value.keys.map((key) => (entry.key, key))) .expand((entry) => entry.value.keys.map((key) => (entry.key, key)))

View file

@ -4,7 +4,6 @@ import "dart:io";
import "dart:isolate"; import "dart:isolate";
import "package:collection/collection.dart"; import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/foundation.dart"; import "package:flutter/foundation.dart";
import "package:nexus/controllers/account_data.dart"; import "package:nexus/controllers/account_data.dart";
import "package:nexus/controllers/accounts.dart"; import "package:nexus/controllers/accounts.dart";
@ -22,9 +21,7 @@ import "package:nexus/controllers/typing.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart"; import "package:nexus/helpers/extensions/gomuks_buffer.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:ffi/ffi.dart"; import "package:ffi/ffi.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/gomuks_event.dart";
import "package:nexus/models/events_decrypted.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/src/third_party/gomuks.g.dart"; import "package:nexus/src/third_party/gomuks.g.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
@ -42,100 +39,43 @@ class GomuksListenerController extends AsyncNotifier<void> {
int requestId, int requestId,
GomuksOwnedBuffer data, GomuksOwnedBuffer data,
) { ) {
final type = command.cast<Utf8>().toDartString();
try { try {
final muksEventType = command.cast<Utf8>().toDartString(); debugPrint("Handling $type...");
debugPrint("Handling $muksEventType..."); switch (GomuksEvent.parse(type, data.toJsonAndFree())) {
final decodedMuksEvent = data.toJsonAndFree(); case ClientStateEvent(:final state):
ref.read(ClientStateController.provider.notifier).set(state);
switch (muksEventType) { case SyncStatusEvent(:final status):
case "client_state": ref.read(SyncStatusController.provider.notifier).set(status);
ref case InitCompleteEvent():
.read(ClientStateController.provider.notifier)
.set(.fromJson(decodedMuksEvent));
case "sync_status":
ref
.read(SyncStatusController.provider.notifier)
.set(.fromJson(decodedMuksEvent));
case "init_complete":
ref.read(InitCompleteController.provider.notifier).complete(); ref.read(InitCompleteController.provider.notifier).complete();
case "send_complete": case SendCompleteEvent(:final event):
final event = Event.fromJson(decodedMuksEvent["event"]); ref.read(RoomsController.provider.notifier).storeEvents([
event,
]);
case TypingEvent(:final typing):
ref.read(TypingController.provider.notifier).set(typing);
case EventsDecryptedEvent(:final decrypted):
ref ref
.read(RoomsController.provider.notifier) .read(RoomsController.provider.notifier)
.update( .handleDecrypted(decrypted);
.new({ case SyncCompleteEvent(:final sync):
event.roomId: .new(events: .new({event.rowId: event})), ref.read(RoomsController.provider.notifier).handleSync(sync);
}),
);
case "typing":
ref ref
.read(TypingController.provider.notifier) .read(AccountDataController.provider.notifier)
.set(.fromJson(decodedMuksEvent)); .handleSync(sync);
case "events_decrypted": ref.read(InvitesController.provider.notifier).handleSync(sync);
final decrypted = EventsDecrypted.fromJson(decodedMuksEvent); ref.read(TypingController.provider.notifier).handleSync(sync);
final metadata = ref
.read(RoomsController.provider)[decrypted.roomId]
?.metadata;
ref ref
.read(RoomsController.provider.notifier) .read(TopLevelSpacesController.provider.notifier)
.update( .handleSync(sync);
.new({
decrypted.roomId: .new(
events: decrypted.events,
metadata: metadata?.copyWith(
previewEventRowID:
decrypted.previewEventRowID ??
metadata.previewEventRowID,
sortingTimestamp:
decrypted.sortingTimestamp ??
metadata.sortingTimestamp,
),
),
}),
);
case "sync_complete":
final syncData = SyncData.fromJson(decodedMuksEvent);
final roomProvider = RoomsController.provider;
final accountDataProvider = AccountDataController.provider;
final invitesProvider = InvitesController.provider;
if (syncData.clearState) {
ref.invalidate(roomProvider);
ref.invalidate(accountDataProvider);
ref.invalidate(invitesProvider);
ref.invalidate(TypingController.provider);
}
ref ref
.read(roomProvider.notifier) .read(SpaceEdgesController.provider.notifier)
.update(syncData.rooms, syncData.leftRooms); .handleSync(sync);
ref case UnknownGomuksEvent():
.read(accountDataProvider.notifier) debugPrint("Unhandled event: $type");
.update(syncData.accountData);
ref
.read(invitesProvider.notifier)
.update(
syncData.invitedRooms,
joinedRooms: syncData.rooms.keys.toISet(),
leftRooms: syncData.leftRooms,
);
if (syncData.topLevelSpaces case final topLevelSpaces?) {
ref
.read(TopLevelSpacesController.provider.notifier)
.set(topLevelSpaces);
}
if (syncData.spaceEdges case final spaceEdges?) {
ref
.read(SpaceEdgesController.provider.notifier)
.set(spaceEdges);
}
default:
debugPrint("Unhandled event: $muksEventType");
} }
debugPrint("Finished handling $muksEventType..."); debugPrint("Finished handling $type...");
} catch (error, stackTrace) { } catch (error, stackTrace) {
if (kDebugMode) { if (kDebugMode) {
debugPrintStack(stackTrace: stackTrace, label: error.toString()); debugPrintStack(stackTrace: stackTrace, label: error.toString());

View file

@ -17,6 +17,7 @@ import "package:nexus/models/content/name.dart";
import "package:nexus/models/content/topic.dart"; import "package:nexus/models/content/topic.dart";
import "package:nexus/models/invited_room.dart"; import "package:nexus/models/invited_room.dart";
import "package:nexus/models/room_summary.dart"; import "package:nexus/models/room_summary.dart";
import "package:nexus/models/sync_data.dart";
/// Pending invites, newest first. /// Pending invites, newest first.
class InvitesController extends Notifier<IList<RoomSummary>> { class InvitesController extends Notifier<IList<RoomSummary>> {
@ -103,6 +104,15 @@ class InvitesController extends Notifier<IList<RoomSummary>> {
.removeWhere((invite) => removed.contains(invite.roomId)); .removeWhere((invite) => removed.contains(invite.roomId));
} }
void handleSync(SyncData sync) {
if (sync.clearState) state = .new();
update(
sync.invitedRooms,
joinedRooms: sync.rooms.keys.toISet(),
leftRooms: sync.leftRooms,
);
}
void remove(String roomId) => void remove(String roomId) =>
state = state.removeWhere((invite) => invite.roomId == roomId); state = state.removeWhere((invite) => invite.roomId == roomId);

View file

@ -4,8 +4,10 @@ import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/events_decrypted.dart";
import "package:nexus/models/read_receipt.dart"; import "package:nexus/models/read_receipt.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/models/sync_data.dart";
class RoomsController extends Notifier<IMap<String, Room>> { class RoomsController extends Notifier<IMap<String, Room>> {
@override @override
@ -82,6 +84,29 @@ class RoomsController extends Notifier<IMap<String, Room>> {
), ),
); );
void handleSync(SyncData sync) {
if (sync.clearState) state = .new();
update(sync.rooms, sync.leftRooms);
}
void handleDecrypted(EventsDecrypted decrypted) {
final metadata = state[decrypted.roomId]?.metadata;
update(
.new({
decrypted.roomId: .new(
events: decrypted.events,
metadata: metadata?.copyWith(
previewEventRowID:
decrypted.previewEventRowID ?? metadata.previewEventRowID,
sortingTimestamp:
decrypted.sortingTimestamp ?? metadata.sortingTimestamp,
),
),
}),
);
}
static IMap<String, ISet<int>> addRelations( static IMap<String, ISet<int>> addRelations(
IMap<String, ISet<int>> relations, IMap<String, ISet<int>> relations,
Iterable<Event> events, Iterable<Event> events,

View file

@ -1,13 +1,13 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/space_edge.dart"; import "package:nexus/models/space_edge.dart";
import "package:nexus/models/sync_data.dart";
class SpaceEdgesController extends Notifier<IMap<String, IList<SpaceEdge>>> { class SpaceEdgesController extends Notifier<IMap<String, IList<SpaceEdge>>> {
@override @override
IMap<String, IList<SpaceEdge>> build() => .new(); IMap<String, IList<SpaceEdge>> build() => .new();
void set(IMap<String, IList<SpaceEdge>> newEdges) => void handleSync(SyncData sync) => state = sync.spaceEdges ?? state;
state = state.addAll(newEdges);
static final provider = static final provider =
NotifierProvider<SpaceEdgesController, IMap<String, IList<SpaceEdge>>>( NotifierProvider<SpaceEdgesController, IMap<String, IList<SpaceEdge>>>(

View file

@ -1,11 +1,12 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/sync_data.dart";
class TopLevelSpacesController extends Notifier<IList<String>> { class TopLevelSpacesController extends Notifier<IList<String>> {
@override @override
IList<String> build() => .new(); IList<String> build() => .new();
void set(IList<String> newSpaces) => state = newSpaces; void handleSync(SyncData sync) => state = sync.topLevelSpaces ?? state;
static final provider = static final provider =
NotifierProvider<TopLevelSpacesController, IList<String>>( NotifierProvider<TopLevelSpacesController, IList<String>>(

View file

@ -1,5 +1,6 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/models/typing.dart"; import "package:nexus/models/typing.dart";
class TypingController extends Notifier<IMap<String, IList<String>>> { class TypingController extends Notifier<IMap<String, IList<String>>> {
@ -8,6 +9,10 @@ class TypingController extends Notifier<IMap<String, IList<String>>> {
void set(Typing typing) => state = state.add(typing.roomId, typing.userIds); void set(Typing typing) => state = state.add(typing.roomId, typing.userIds);
void handleSync(SyncData sync) {
if (sync.clearState) state = .new();
}
static final provider = static final provider =
NotifierProvider<TypingController, IMap<String, IList<String>>>( NotifierProvider<TypingController, IMap<String, IList<String>>>(
TypingController.new, TypingController.new,

View file

@ -0,0 +1,34 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/client_state.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/events_decrypted.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/models/sync_status.dart";
import "package:nexus/models/typing.dart";
part "gomuks_event.freezed.dart";
@freezed
sealed class GomuksEvent with _$GomuksEvent {
const factory GomuksEvent.clientState(ClientState state) = ClientStateEvent;
const factory GomuksEvent.syncStatus(SyncStatus status) = SyncStatusEvent;
const factory GomuksEvent.initComplete() = InitCompleteEvent;
const factory GomuksEvent.sendComplete(Event event) = SendCompleteEvent;
const factory GomuksEvent.typing(Typing typing) = TypingEvent;
const factory GomuksEvent.eventsDecrypted(EventsDecrypted decrypted) =
EventsDecryptedEvent;
const factory GomuksEvent.syncComplete(SyncData sync) = SyncCompleteEvent;
const factory GomuksEvent.unknown(String type) = UnknownGomuksEvent;
factory GomuksEvent.parse(String type, Map<String, dynamic> json) =>
switch (type) {
"client_state" => .clientState(.fromJson(json)),
"sync_status" => .syncStatus(.fromJson(json)),
"init_complete" => const .initComplete(),
"send_complete" => .sendComplete(.fromJson(json["event"])),
"typing" => .typing(.fromJson(json)),
"events_decrypted" => .eventsDecrypted(.fromJson(json)),
"sync_complete" => .syncComplete(.fromJson(json)),
_ => .unknown(type),
};
}