This commit is contained in:
Henry Hiles 2026-01-27 01:13:02 +00:00
commit f51d773885
No known key found for this signature in database
12 changed files with 182 additions and 156 deletions

View file

@ -1,11 +1,12 @@
import "dart:convert";
import "dart:developer";
import "dart:ffi";
import "dart:isolate";
import "package:ffi/ffi.dart";
import "package:flutter/foundation.dart";
import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/sync_status_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
import "package:nexus/models/client_state.dart";
import "package:nexus/models/login.dart";
@ -18,55 +19,64 @@ class ClientController extends AsyncNotifier<int> {
@override
Future<int> build() async {
final handle = await Isolate.run(GomuksInit);
ref.onDispose(() => GomuksDestroy(handle));
final errorCode = GomuksStart(
handle,
NativeCallable<
Void Function(Pointer<Char>, Int64, GomuksOwnedBuffer)
>.listener((
Pointer<Char> command,
int requestId,
GomuksOwnedBuffer data,
) {
try {
final muksEventType = command.cast<Utf8>().toDartString();
debugPrint("Handling $muksEventType...");
final Map<String, dynamic> decodedMuksEvent = data.toJson();
final callable =
NativeCallable<
Void Function(Pointer<Char>, Int64, GomuksOwnedBuffer)
>.listener((
Pointer<Char> command,
int requestId,
GomuksOwnedBuffer data,
) {
try {
final muksEventType = command.cast<Utf8>().toDartString();
debugPrint("Handling $muksEventType...");
final Map<String, dynamic> decodedMuksEvent = data.toJson();
switch (muksEventType) {
case "client_state":
ref
.watch(ClientStateController.provider.notifier)
.set(ClientState.fromJson(decodedMuksEvent));
break;
case "sync_status":
ref
.watch(SyncStatusController.provider.notifier)
.set(SyncStatus.fromJson(decodedMuksEvent));
break;
case "sync_complete":
final syncData = SyncData.fromJson(decodedMuksEvent);
debugPrint(jsonEncode(syncData.toJson()));
switch (muksEventType) {
case "client_state":
ref
.watch(ClientStateController.provider.notifier)
.set(ClientState.fromJson(decodedMuksEvent));
break;
case "sync_status":
ref
.watch(SyncStatusController.provider.notifier)
.set(SyncStatus.fromJson(decodedMuksEvent));
break;
case "sync_complete":
final syncData = SyncData.fromJson(decodedMuksEvent);
final roomProvider = RoomsController.provider;
// ref
// .watch(SyncStatusController.provider.notifier)
// .set(SyncStatus.fromJson(decodedMuksEvent));
break;
case "typing":
//TODO: IMPL
break;
default:
debugPrint("Unhandled event: $muksEventType");
}
debugPrint("Finished handling $muksEventType...");
} catch (error, stackTrace) {
debugger();
debugPrintStack(stackTrace: stackTrace, label: error.toString());
if (syncData.clearState) ref.invalidate(roomProvider);
ref
.watch(roomProvider.notifier)
.update(syncData.rooms, syncData.leftRooms);
ref
.watch(TopLevelSpacesController.provider.notifier)
.set(syncData.topLevelSpaces);
// ref
// .watch(SyncStatusController.provider.notifier)
// .set(SyncStatus.fromJson(decodedMuksEvent));
break;
case "typing":
//TODO: IMPL
break;
default:
debugPrint("Unhandled event: $muksEventType");
}
})
.nativeFunction,
);
debugPrint("Finished handling $muksEventType...");
} catch (error, stackTrace) {
debugger();
debugPrintStack(stackTrace: stackTrace, label: error.toString());
}
});
ref.onDispose(() => GomuksDestroy(handle));
ref.onDispose(callable.close);
final errorCode = GomuksStart(handle, callable.nativeFunction);
if (errorCode == 0) return handle;
throw Exception("GomuksStart returned error code $errorCode");

View file

@ -1,23 +1,57 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/helpers/extensions/get_full_room.dart";
import "package:nexus/models/full_room.dart";
import "package:nexus/models/read_receipt.dart";
import "package:nexus/models/room.dart";
class RoomsController extends AsyncNotifier<IList<FullRoom>> {
class RoomsController extends Notifier<IMap<String, Room>> {
@override
Future<IList<FullRoom>> build() async {
final client = await ref.watch(ClientController.provider.future);
IMap<String, Room> build() => const IMap.empty();
ref.onDispose(
client.onSync.stream.listen((_) => ref.invalidateSelf()).cancel,
void update(IMap<String, Room> rooms, ISet<String> leftRooms) {
final merged = rooms.entries.fold(state, (acc, entry) {
final roomId = entry.key;
final incoming = entry.value;
final existing = acc[roomId];
return acc.add(
roomId,
existing?.copyWith(
metadata: incoming.metadata ?? existing.metadata,
events: existing.events.addAll(incoming.events),
state: incoming.state.entries.fold(
existing.state,
(stateAcc, event) => stateAcc.add(
event.key,
(stateAcc[event.key] ?? IMap<dynamic, dynamic>()).addAll(
event.value,
),
),
),
timeline: incoming.reset
? incoming.timeline
: existing.timeline.addAll(incoming.timeline),
receipts: incoming.receipts.entries.fold(
existing.receipts,
(receiptAcc, event) => receiptAcc.add(
event.key,
(receiptAcc[event.key] ?? IList<ReadReceipt>()).addAll(
event.value,
),
),
),
) ??
incoming,
);
});
final prunedList = leftRooms.fold(
merged,
(acc, roomId) => acc.remove(roomId),
);
return IList(await Future.wait(client.rooms.map((room) => room.fullRoom)));
state = prunedList;
}
static final provider =
AsyncNotifierProvider<RoomsController, IList<FullRoom>>(
RoomsController.new,
);
static final provider = NotifierProvider<RoomsController, IMap<String, Room>>(
RoomsController.new,
);
}

View file

@ -2,72 +2,50 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/helpers/extensions/get_full_room.dart";
import "package:nexus/helpers/extensions/room_to_children.dart";
import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/top_level_spaces_controller.dart";
import "package:nexus/models/space.dart";
class SpacesController extends AsyncNotifier<IList<Space>> {
@override
Future<IList<Space>> build() async {
final client = await ref.watch(ClientController.provider.future);
final topLevelSpaceIds = ref.watch(TopLevelSpacesController.provider);
final rooms = ref.watch(RoomsController.provider);
ref.onDispose(
client.onSync.stream.listen((_) => ref.invalidateSelf()).cancel,
);
final topLevelSpaces = topLevelSpaceIds
.map((id) => rooms[id])
.nonNulls
.toIList();
final topLevel = IList(
await Future.wait(
client.rooms
.where((room) => !room.isDirectChat)
.where(
(room) => client.rooms
.where((room) => room.isSpace)
.every(
(match) => match.spaceChildren.every(
(child) => child.roomId != room.id,
),
),
)
.map((room) => room.fullRoom),
),
);
final dmRooms = rooms.values
.where((room) => room.metadata?.dmUserId != null)
.toIList();
final topLevelSpaces = topLevel.where((r) => r.roomData.isSpace).toIList();
final topLevelRooms = topLevel.where((r) => !r.roomData.isSpace).toIList();
final topLevelRooms = rooms.values
.where((room) => room.metadata?.dmUserId == null)
.where(
(room) => spaceRooms.every(
(space) =>
space.spaceChildren.every((child) => child.roomId != room.id),
),
)
.toIList();
// 4 Combine all into a single IList
return IList([
Space(
client: client,
title: "Home",
id: "home",
title: "Home",
children: topLevelRooms,
icon: Icons.home,
),
Space(
client: client,
title: "Direct Messages",
id: "dms",
children: IList(
await Future.wait(
client.rooms
.where((room) => room.isDirectChat)
.map((room) => room.fullRoom),
),
),
title: "Direct Messages",
children: dmRooms,
icon: Icons.person,
),
...(await Future.wait(
topLevelSpaces.map(
(space) async => Space(
client: client,
title: space.title,
avatar: space.avatar,
id: space.roomData.id,
roomData: space.roomData,
children: IList(await space.roomData.getAllChildren()),
),
),
)),
...topLevelSpaces,
]);
}

View file

@ -0,0 +1,14 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
class TopLevelSpacesController extends Notifier<IList<String>> {
@override
IList<String> build() => const IList.empty();
void set(IList<String> newSpaces) => state = newSpaces;
static final provider =
NotifierProvider<TopLevelSpacesController, IList<String>>(
TopLevelSpacesController.new,
);
}