import "dart:developer"; import "dart:ffi"; import "dart:isolate"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; 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/space_edges_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/event.dart"; import "package:nexus/models/paginate.dart"; import "package:nexus/models/requests/get_event_request.dart"; import "package:nexus/models/requests/get_related_events_request.dart"; import "package:nexus/models/requests/get_room_state_request.dart"; import "package:nexus/models/requests/login_request.dart"; import "package:nexus/models/profile.dart"; import "package:nexus/models/requests/paginate_request.dart"; import "package:nexus/models/requests/redact_event_request.dart"; import "package:nexus/models/requests/report_request.dart"; import "package:nexus/models/requests/send_message_request.dart"; import "package:nexus/models/room.dart"; import "package:nexus/models/sync_data.dart"; import "package:nexus/models/sync_status.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; class ClientController extends AsyncNotifier { @override Future build() async { final handle = await Isolate.run(GomuksInit); final callable = NativeCallable< Void Function(Pointer, Int64, GomuksOwnedBuffer) >.listener(( Pointer command, int requestId, GomuksOwnedBuffer data, ) { try { final muksEventType = command.cast().toDartString(); debugPrint("Handling $muksEventType..."); final 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); final roomProvider = RoomsController.provider; if (syncData.clearState) ref.invalidate(roomProvider); ref .watch(roomProvider.notifier) .update(syncData.rooms, syncData.leftRooms); if (syncData.topLevelSpaces != null) { ref .watch(TopLevelSpacesController.provider.notifier) .set(syncData.topLevelSpaces!); } if (syncData.spaceEdges != null) { ref .watch(SpaceEdgesController.provider.notifier) .set(syncData.spaceEdges!); } // 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()); } }); 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"); } Future _sendCommand( String command, Map data, ) async { final bufferPointer = data.toGomuksBufferPtr(); final handle = await future; final response = await Isolate.run( () => GomuksSubmitCommand( handle, command.toNativeUtf8().cast(), bufferPointer.ref, ), ); calloc.free(bufferPointer); final json = response.buf.toJson(); if (json is String) throw json; return json; } Future redactEvent(RedactEventRequest report) => _sendCommand("redact_event", report.toJson()); Future sendMessage(SendMessageRequest request) => _sendCommand("send_message", request.toJson()); Future verify(String recoveryKey) async { try { await _sendCommand("verify", {"recovery_key": recoveryKey}); return true; } catch (error) { return false; } } Future leaveRoom(Room room) async { if (room.metadata == null) return; await _sendCommand("leave_room", {"room_id": room.metadata!.id}); } Future> getRoomState(GetRoomStateRequest request) async { final response = (await _sendCommand("get_room_state", request.toJson())) as List; return response.map((event) => Event.fromJson(event)).toIList(); } Future?> getRelatedEvents( GetRelatedEventsRequest request, ) async { final response = (await _sendCommand("get_related_events", request.toJson())) as List?; return response?.map((event) => Event.fromJson(event)).toIList(); } Future getEvent(GetEventRequest request) async { final json = await _sendCommand("get_event", request.toJson()); return json == null ? null : Event.fromJson(json); } Future paginate(PaginateRequest request) async => Paginate.fromJson(await _sendCommand("paginate", request.toJson())); Future getProfile(String userId) async => Profile.fromJson(await _sendCommand("get_profile", {"user_id": userId})); Future reportEvent(ReportRequest report) => _sendCommand("report_event", report.toJson()); Future markRead(Room room) async { if (room.events.isEmpty || room.metadata == null) return; await _sendCommand("mark_read", { "room_id": room.metadata?.id, "receipt_type": "m.read", "event_id": room.events.last.eventId, }); } Future login(LoginRequest login) async { try { await _sendCommand("login", login.toJson()); return true; } catch (error) { return false; } } Future discoverHomeserver(Uri homeserver) async { try { final response = await _sendCommand("discover_homeserver", { "user_id": "@fakeuser:${homeserver.host}", }); return response["m.homeserver"]?["base_url"]; } catch (error) { return null; } } static final provider = AsyncNotifierProvider( ClientController.new, ); }