working message rendering

This commit is contained in:
Henry Hiles 2026-01-28 14:17:18 +00:00
commit e59632bb07
No known key found for this signature in database
20 changed files with 305 additions and 197 deletions

View file

@ -1,6 +1,7 @@
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";
@ -10,8 +11,12 @@ 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";
import "package:nexus/models/report.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/get_event_request.dart";
import "package:nexus/models/get_related_events_request.dart";
import "package:nexus/models/login_request.dart";
import "package:nexus/models/profile.dart";
import "package:nexus/models/report_request.dart";
import "package:nexus/models/room.dart";
import "package:nexus/models/sync_data.dart";
import "package:nexus/models/sync_status.dart";
@ -34,7 +39,7 @@ class ClientController extends AsyncNotifier<int> {
try {
final muksEventType = command.cast<Utf8>().toDartString();
debugPrint("Handling $muksEventType...");
final Map<String, dynamic> decodedMuksEvent = data.toJson();
final decodedMuksEvent = data.toJson();
switch (muksEventType) {
case "client_state":
@ -92,10 +97,7 @@ class ClientController extends AsyncNotifier<int> {
throw Exception("GomuksStart returned error code $errorCode");
}
Future<Map<String, dynamic>> sendCommand(
String command,
Map<String, dynamic> data,
) async {
Future<dynamic> sendCommand(String command, Map<String, dynamic> data) async {
final bufferPointer = data.toGomuksBufferPtr();
final handle = await future;
final response = await Isolate.run(
@ -125,7 +127,27 @@ class ClientController extends AsyncNotifier<int> {
await sendCommand("leave_room", {"room_id": room.metadata!.id});
}
Future<void> reportEvent(Report report) =>
Future<IList<Event>?> getRelatedEvents(
GetRelatedEventsRequest request,
) async {
final response =
(await sendCommand("get_related_events", request.toJson())) as List?;
return response?.map((event) => Event.fromJson(event)).toIList();
}
Future<Event?> getEvent(GetEventRequest request) async {
final json = await sendCommand("get_event", request.toJson());
return json == null ? null : Event.fromJson(json);
}
Future<Profile?> getProfile(String userId) async {
final json = await sendCommand("get_profile", {"user_id": userId});
return json == null ? null : Profile.fromJson(json);
}
Future<void> reportEvent(ReportRequest report) =>
sendCommand("report_event", report.toJson());
Future<void> markRead(Room room) async {
@ -137,7 +159,7 @@ class ClientController extends AsyncNotifier<int> {
});
}
Future<bool> login(Login login) async {
Future<bool> login(LoginRequest login) async {
try {
await sendCommand("login", login.toJson());
return true;
@ -151,7 +173,7 @@ class ClientController extends AsyncNotifier<int> {
final response = await sendCommand("discover_homeserver", {
"user_id": "@fakeuser:${homeserver.host}",
});
return (response["m.homeserver"] as Map<String, dynamic>)["base_url"];
return response["m.homeserver"]?["base_url"];
} catch (error) {
return null;
}

View file

@ -1,18 +0,0 @@
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:matrix/matrix.dart";
class EventsController extends AsyncNotifier<Timeline> {
EventsController(this.room);
final Room room;
@override
Future<Timeline> build({String? from}) => room.getTimeline();
Future<void> prev() async {
final timeline = await future;
await timeline.requestHistory();
}
static final provider = AsyncNotifierProvider.autoDispose
.family<EventsController, Timeline, Room>(EventsController.new);
}

View file

@ -0,0 +1,18 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/models/event.dart";
class NewEventsController extends Notifier<IList<Event>> {
final String roomId;
NewEventsController(this.roomId);
@override
IList<Event> build() => const IList.empty();
void add(IList<Event> newEvents) => state = newEvents;
static final provider = NotifierProvider.autoDispose
.family<NewEventsController, IList<Event>, String>(
NewEventsController.new,
);
}

View file

@ -1,65 +1,72 @@
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_chat_core/flutter_chat_core.dart" as chat;
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:fluttertagger/fluttertagger.dart" as tagger;
import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/new_events_controller.dart";
import "package:nexus/controllers/selected_room_controller.dart";
import "package:nexus/helpers/extensions/event_to_message.dart";
import "package:nexus/helpers/extensions/list_to_messages.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/room.dart";
class RoomChatController extends AsyncNotifier<ChatController> {
final Room room;
RoomChatController(this.room);
final String roomId;
RoomChatController(this.roomId);
@override
Future<ChatController> build() async {
// final timeline = await ref.watch(EventsController.provider(room).future);
final client = ref.watch(ClientController.provider.notifier);
final events =
ref.read(SelectedRoomController.provider)?.events ??
const IList.empty();
return InMemoryChatController();
ref.onDispose(
ref.listen(NewEventsController.provider(roomId), (_, next) async {
for (final event in next) {
if (event.type == "m.room.redaction") {
final controller = await future;
final message = controller.messages.firstWhereOrNull(
(message) => message.id == event.content["redacts"],
);
if (message == null) return;
// ref.onDispose(
// room.client.onTimelineEvent.stream.listen((event) async {
// if (event.roomId != room.metadata.id) return;
await controller.removeMessage(message);
} else {
final message = await event.toMessage(client, includeEdits: true);
if (event.relationType == "m.replace") {
final controller = await future;
final oldMessage = controller.messages.firstWhereOrNull(
(element) => element.id == event.relatesTo,
);
if (oldMessage == null || message == null) return;
// if (event.type == "m.room.redaction") {
// final controller = await future;
// final message = controller.messages.firstWhereOrNull(
// (message) => message.id == event.redacts,
// );
// if (message == null) return;
return await updateMessage(
oldMessage,
message.copyWith(
id: oldMessage.id,
replyToMessageId: oldMessage.replyToMessageId,
metadata: {
...(oldMessage.metadata ?? {}),
...(message.metadata ?? {})
.toIMap()
.where((key, value) => value != null)
.unlock,
},
),
);
}
if (message != null) {
return await insertMessage(message);
}
}
}
}).close,
);
// await controller.removeMessage(message);
// } else {
// final message = await event.toMessage(includeEdits: true, timeline);
// if (event.relationshipType == RelationshipTypes.edit) {
// final controller = await future;
// final oldMessage = controller.messages.firstWhereOrNull(
// (element) => element.id == event.relationshipEventId,
// );
// if (oldMessage == null || message == null) return;
// return await updateMessage(
// oldMessage,
// message.copyWith(
// id: oldMessage.id,
// replyToMessageId: oldMessage.replyToMessageId,
// metadata: {
// ...(oldMessage.metadata ?? {}),
// ...((message.metadata ?? {}).filterMap(
// (key, value) => value == null ? null : MapEntry(key, value),
// )),
// },
// ),
// );
// }
// if (message != null) {
// return await insertMessage(message);
// }
// }
// }).cancel,
// );
// return InMemoryChatController(
// messages: await timeline.events.toMessages(room, timeline),
// );
final messages = await events.toMessages(client);
return InMemoryChatController(messages: messages);
}
Future<void> insertMessage(Message message) async {
@ -145,8 +152,8 @@ class RoomChatController extends AsyncNotifier<ChatController> {
);
}
static final provider = AsyncNotifierProvider.family
.autoDispose<RoomChatController, ChatController, Room>(
static final provider =
AsyncNotifierProvider.family<RoomChatController, ChatController, String>(
RoomChatController.new,
);
}

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/controllers/new_events_controller.dart";
import "package:nexus/models/read_receipt.dart";
import "package:nexus/models/room.dart";
@ -13,11 +14,18 @@ class RoomsController extends Notifier<IMap<String, Room>> {
final incoming = entry.value;
final existing = acc[roomId];
ref
.watch(NewEventsController.provider(roomId).notifier)
.add(incoming.events);
return acc.add(
roomId,
existing?.copyWith(
metadata: incoming.metadata ?? existing.metadata,
events: existing.events.addAll(incoming.events),
events: existing.events.updateById(
incoming.events,
(item) => item.eventId,
),
state: incoming.state.entries.fold(
existing.state,
(stateAcc, event) => stateAcc.add(