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/push_rules.dart";
import "package:nexus/models/requests/update_push_rule.dart";
import "package:nexus/models/sync_data.dart";
class AccountDataController extends Notifier<AccountData> {
@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) {
ISet<(String, String)> keysOf(ImagePackRooms rooms) => rooms.rooms.entries
.expand((entry) => entry.value.keys.map((key) => (entry.key, key)))

View file

@ -4,7 +4,6 @@ import "dart:io";
import "dart:isolate";
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/foundation.dart";
import "package:nexus/controllers/account_data.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/main.dart";
import "package:ffi/ffi.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/gomuks_event.dart";
import "package:nexus/src/third_party/gomuks.g.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
@ -42,100 +39,43 @@ class GomuksListenerController extends AsyncNotifier<void> {
int requestId,
GomuksOwnedBuffer data,
) {
final type = command.cast<Utf8>().toDartString();
try {
final muksEventType = command.cast<Utf8>().toDartString();
debugPrint("Handling $muksEventType...");
final decodedMuksEvent = data.toJsonAndFree();
switch (muksEventType) {
case "client_state":
ref
.read(ClientStateController.provider.notifier)
.set(.fromJson(decodedMuksEvent));
case "sync_status":
ref
.read(SyncStatusController.provider.notifier)
.set(.fromJson(decodedMuksEvent));
case "init_complete":
debugPrint("Handling $type...");
switch (GomuksEvent.parse(type, data.toJsonAndFree())) {
case ClientStateEvent(:final state):
ref.read(ClientStateController.provider.notifier).set(state);
case SyncStatusEvent(:final status):
ref.read(SyncStatusController.provider.notifier).set(status);
case InitCompleteEvent():
ref.read(InitCompleteController.provider.notifier).complete();
case "send_complete":
final event = Event.fromJson(decodedMuksEvent["event"]);
case SendCompleteEvent(:final event):
ref.read(RoomsController.provider.notifier).storeEvents([
event,
]);
case TypingEvent(:final typing):
ref.read(TypingController.provider.notifier).set(typing);
case EventsDecryptedEvent(:final decrypted):
ref
.read(RoomsController.provider.notifier)
.update(
.new({
event.roomId: .new(events: .new({event.rowId: event})),
}),
);
case "typing":
.handleDecrypted(decrypted);
case SyncCompleteEvent(:final sync):
ref.read(RoomsController.provider.notifier).handleSync(sync);
ref
.read(TypingController.provider.notifier)
.set(.fromJson(decodedMuksEvent));
case "events_decrypted":
final decrypted = EventsDecrypted.fromJson(decodedMuksEvent);
final metadata = ref
.read(RoomsController.provider)[decrypted.roomId]
?.metadata;
ref
.read(RoomsController.provider.notifier)
.update(
.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
.read(roomProvider.notifier)
.update(syncData.rooms, syncData.leftRooms);
ref
.read(accountDataProvider.notifier)
.update(syncData.accountData);
ref
.read(invitesProvider.notifier)
.update(
syncData.invitedRooms,
joinedRooms: syncData.rooms.keys.toISet(),
leftRooms: syncData.leftRooms,
);
if (syncData.topLevelSpaces case final topLevelSpaces?) {
.read(AccountDataController.provider.notifier)
.handleSync(sync);
ref.read(InvitesController.provider.notifier).handleSync(sync);
ref.read(TypingController.provider.notifier).handleSync(sync);
ref
.read(TopLevelSpacesController.provider.notifier)
.set(topLevelSpaces);
}
if (syncData.spaceEdges case final spaceEdges?) {
.handleSync(sync);
ref
.read(SpaceEdgesController.provider.notifier)
.set(spaceEdges);
.handleSync(sync);
case UnknownGomuksEvent():
debugPrint("Unhandled event: $type");
}
default:
debugPrint("Unhandled event: $muksEventType");
}
debugPrint("Finished handling $muksEventType...");
debugPrint("Finished handling $type...");
} catch (error, stackTrace) {
if (kDebugMode) {
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/invited_room.dart";
import "package:nexus/models/room_summary.dart";
import "package:nexus/models/sync_data.dart";
/// Pending invites, newest first.
class InvitesController extends Notifier<IList<RoomSummary>> {
@ -103,6 +104,15 @@ class InvitesController extends Notifier<IList<RoomSummary>> {
.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) =>
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:flutter_riverpod/flutter_riverpod.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/room.dart";
import "package:nexus/models/sync_data.dart";
class RoomsController extends Notifier<IMap<String, Room>> {
@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(
IMap<String, ISet<int>> relations,
Iterable<Event> events,

View file

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

View file

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

View file

@ -1,5 +1,6 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/models/typing.dart";
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 handleSync(SyncData sync) {
if (sync.clearState) state = .new();
}
static final provider =
NotifierProvider<TypingController, IMap<String, IList<String>>>(
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),
};
}