From 7c76bb6e669469d3458f8458f5d8f6b821f4588e Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 28 Jan 2026 14:17:18 +0000 Subject: [PATCH] working message rendering --- lib/controllers/client_controller.dart | 42 ++++-- lib/controllers/events_controller.dart | 18 --- lib/controllers/new_events_controller.dart | 18 +++ lib/controllers/room_chat_controller.dart | 105 ++++++++------- lib/controllers/rooms_controller.dart | 10 +- lib/helpers/extensions/event_to_message.dart | 134 ++++++++++--------- lib/helpers/extensions/gomuks_buffer.dart | 11 +- lib/helpers/extensions/list_to_messages.dart | 12 +- lib/models/event.dart | 2 +- lib/models/get_event_request.dart | 15 +++ lib/models/get_related_events_request.dart | 15 +++ lib/models/login.dart | 14 -- lib/models/login_request.dart | 15 +++ lib/models/profile.dart | 29 ++++ lib/models/report.dart | 14 -- lib/models/report_request.dart | 15 +++ lib/pages/chat_page.dart | 5 +- lib/pages/login_page.dart | 4 +- lib/widgets/chat_page/chat_box.dart | 4 +- lib/widgets/chat_page/room_chat.dart | 14 +- 20 files changed, 302 insertions(+), 194 deletions(-) delete mode 100644 lib/controllers/events_controller.dart create mode 100644 lib/controllers/new_events_controller.dart create mode 100644 lib/models/get_event_request.dart create mode 100644 lib/models/get_related_events_request.dart delete mode 100644 lib/models/login.dart create mode 100644 lib/models/login_request.dart create mode 100644 lib/models/profile.dart delete mode 100644 lib/models/report.dart create mode 100644 lib/models/report_request.dart diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index f0ad4ea..abd7b1f 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -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 { try { final muksEventType = command.cast().toDartString(); debugPrint("Handling $muksEventType..."); - final Map decodedMuksEvent = data.toJson(); + final decodedMuksEvent = data.toJson(); switch (muksEventType) { case "client_state": @@ -92,10 +97,7 @@ class ClientController extends AsyncNotifier { throw Exception("GomuksStart returned error code $errorCode"); } - Future> sendCommand( - String command, - Map data, - ) async { + Future sendCommand(String command, Map data) async { final bufferPointer = data.toGomuksBufferPtr(); final handle = await future; final response = await Isolate.run( @@ -125,7 +127,27 @@ class ClientController extends AsyncNotifier { await sendCommand("leave_room", {"room_id": room.metadata!.id}); } - Future reportEvent(Report report) => + 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 getProfile(String userId) async { + final json = await sendCommand("get_profile", {"user_id": userId}); + + return json == null ? null : Profile.fromJson(json); + } + + Future reportEvent(ReportRequest report) => sendCommand("report_event", report.toJson()); Future markRead(Room room) async { @@ -137,7 +159,7 @@ class ClientController extends AsyncNotifier { }); } - Future login(Login login) async { + Future login(LoginRequest login) async { try { await sendCommand("login", login.toJson()); return true; @@ -151,7 +173,7 @@ class ClientController extends AsyncNotifier { final response = await sendCommand("discover_homeserver", { "user_id": "@fakeuser:${homeserver.host}", }); - return (response["m.homeserver"] as Map)["base_url"]; + return response["m.homeserver"]?["base_url"]; } catch (error) { return null; } diff --git a/lib/controllers/events_controller.dart b/lib/controllers/events_controller.dart deleted file mode 100644 index e7a192d..0000000 --- a/lib/controllers/events_controller.dart +++ /dev/null @@ -1,18 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:matrix/matrix.dart"; - -class EventsController extends AsyncNotifier { - EventsController(this.room); - final Room room; - - @override - Future build({String? from}) => room.getTimeline(); - - Future prev() async { - final timeline = await future; - await timeline.requestHistory(); - } - - static final provider = AsyncNotifierProvider.autoDispose - .family(EventsController.new); -} diff --git a/lib/controllers/new_events_controller.dart b/lib/controllers/new_events_controller.dart new file mode 100644 index 0000000..215ebd3 --- /dev/null +++ b/lib/controllers/new_events_controller.dart @@ -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> { + final String roomId; + NewEventsController(this.roomId); + + @override + IList build() => const IList.empty(); + + void add(IList newEvents) => state = newEvents; + + static final provider = NotifierProvider.autoDispose + .family, String>( + NewEventsController.new, + ); +} diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index ffc8ffc..0012418 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -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 { - final Room room; - RoomChatController(this.room); + final String roomId; + RoomChatController(this.roomId); @override Future 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 insertMessage(Message message) async { @@ -145,8 +152,8 @@ class RoomChatController extends AsyncNotifier { ); } - static final provider = AsyncNotifierProvider.family - .autoDispose( + static final provider = + AsyncNotifierProvider.family( RoomChatController.new, ); } diff --git a/lib/controllers/rooms_controller.dart b/lib/controllers/rooms_controller.dart index 0a41f31..bbeb402 100644 --- a/lib/controllers/rooms_controller.dart +++ b/lib/controllers/rooms_controller.dart @@ -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> { 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( diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index 5591952..4f5d2db 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -1,23 +1,36 @@ -import "package:collection/collection.dart"; +import "dart:developer"; + import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:matrix/matrix.dart"; +import "package:nexus/controllers/client_controller.dart"; +import "package:nexus/models/event.dart"; +import "package:nexus/models/get_event_request.dart"; +import "package:nexus/models/get_related_events_request.dart"; extension EventToMessage on Event { Future toMessage( - Timeline timeline, { + ClientController client, { bool mustBeText = false, bool includeEdits = false, }) async { - final replyId = inReplyToEventId(); - final newEvent = (unsigned?["m.relations"] as Map?)?["m.replace"]; - final event = newEvent == null ? this : Event.fromJson(newEvent, room); + if (relationType == "m.replace" && !includeEdits) return null; + final newEvents = await client.getRelatedEvents( + GetRelatedEventsRequest( + roomId: roomId, + eventId: eventId, + relationType: "m.replace", + ), + ); + final event = newEvents?.lastOrNull ?? this; + + final replyId = this.content["m.relates_to"]?["m.in_reply_to"]?["event_id"]; final replyEvent = replyId == null ? null - : await room.getEventById(replyId); + : await client.getEvent( + GetEventRequest(roomId: roomId, eventId: replyId), + ); - final sender = - await event.fetchSenderUser() ?? event.senderFromMemoryOrFallback; + final author = await client.getProfile(event.authorId); final newContent = event.content["m.new_content"] as Map?; final metadata = { "formatted": @@ -26,109 +39,108 @@ extension EventToMessage on Event { event.content["formatted_body"] ?? event.content["body"] ?? "", - "reply": await replyEvent?.toMessage(mustBeText: true, timeline), + "reply": await replyEvent?.toMessage(client, mustBeText: true), "body": newContent?["body"] ?? event.content["body"], "eventType": event.type, - "avatarUrl": sender.avatarUrl.toString(), - "displayName": sender.displayName ?? sender.id, + "avatarUrl": author?.avatarUrl, + "displayName": author?.displayName ?? authorId, "txnId": transactionId, }; - final editedAt = event.relationshipType == RelationshipTypes.edit - ? event.originServerTs - : null; + final editedAt = event.relationType == "m.replace" ? event.timestamp : null; - if ((redacted && !mustBeText) || - (!includeEdits && (relationshipType == RelationshipTypes.edit))) { + if ((event.redactedBy != null && !mustBeText) || + (!includeEdits && (relationType == "m.replace"))) { return null; } - // TODO: Use server-generated preview if enabled when https://github.com/famedly/matrix-dart-sdk/issues/2195 is fixed. + // TODO: Use server-generated preview if enabled // final match = Uri.tryParse( // RegExp(regexLink, caseSensitive: false).firstMatch(body)?.group(0) ?? "", // ); - // final preview = match == null - // ? null - // : await room.client.getUrlPreview(match); - final asText = Message.text( metadata: metadata, id: eventId, - authorId: senderId, - text: redacted ? "This message has been deleted..." : event.body, + authorId: authorId, + text: redactedBy == null + ? event.content["body"] ?? "" + : "This message has been deleted...", replyToMessageId: replyId, - deliveredAt: originServerTs, + deliveredAt: timestamp, editedAt: editedAt, ) as TextMessage; + final content = (decrypted ?? this.content); + if (mustBeText) return asText; return switch (type) { - EventTypes.Encrypted => asText.copyWith( + "m.room.encrypted" => asText.copyWith( text: "Unable to decrypt message.", metadata: {...metadata, "formatted": "Unable to decrypt message."}, ), - PollEventContent.startType => Message.custom( - metadata: { - ...metadata, - "poll": event.parsedPollEventContent.pollStartContent, - "responses": event.getPollResponses(timeline), - }, - id: eventId, - deliveredAt: originServerTs, - authorId: senderId, - ), - (EventTypes.Sticker || EventTypes.Message) => switch (messageType) { - (MessageTypes.Sticker || MessageTypes.Image) => Message.image( + // "org.matrix.msc3381.poll.start" => Message.custom( + // metadata: { + // ...metadata, + // "poll": event.parsedPollEventContent.pollStartContent, + // "responses": event.getPollResponses(timeline), + // }, + // id: eventId, + // deliveredAt: originServerTs, + // authorId: senderId, + // ), + ("m.sticker" || "m.room.message") => switch (content["msgtype"]) { + ("m.sticker" || "m.image") => Message.image( metadata: metadata, id: eventId, - authorId: senderId, - text: event.text, - source: (await getAttachmentUri()).toString(), + authorId: authorId, + text: event.localContent?.sanitizedHtml, + source: "(await getAttachmentUri()).toString()", // TODO replyToMessageId: replyId, - deliveredAt: originServerTs, + deliveredAt: timestamp, blurhash: (event.content["info"] as Map?)?["xyz.amorgan.blurhash"], ), - MessageTypes.Audio => Message.audio( + "m.audio" => Message.audio( metadata: metadata, id: eventId, - authorId: senderId, - text: event.text, + authorId: authorId, + text: event.content["body"], replyToMessageId: replyId, - source: (await event.getAttachmentUri()).toString(), - deliveredAt: originServerTs, + source: "(await event.getAttachmentUri()).toString()", // TODO + deliveredAt: timestamp, // TODO: See if we can figure out duration duration: Duration(hours: 1), ), - MessageTypes.File => Message.file( + "m.file" => Message.file( name: event.content["filename"].toString(), metadata: metadata, id: eventId, - authorId: senderId, - source: (await event.getAttachmentUri()).toString(), + authorId: authorId, + source: "(await event.getAttachmentUri()).toString()", // TODO replyToMessageId: replyId, - deliveredAt: originServerTs, + deliveredAt: timestamp, ), _ => asText, }, - EventTypes.RoomMember => Message.system( + "m.room.member" => Message.system( metadata: metadata, id: eventId, - authorId: senderId, + authorId: authorId, + deliveredAt: timestamp, text: - "${event.asUser.displayName ?? event.asUser.id} ${switch (Membership.values.firstWhereOrNull((membership) => membership.name == event.content["membership"])) { - Membership.invite => "was invited to", - Membership.join => "joined", - Membership.leave => "left", - Membership.knock => "asked to join", - Membership.ban => "was banned from", + "${content["displayname"] ?? event.stateKey} ${switch (event.content["membership"]) { + "invite" => "was invited to", + "join" => "joined", + "leave" => "left", + "knock" => "asked to join", + "ban" => "was banned from", _ => "did something relating to", }} the room.", ), - EventTypes.Redaction => null, + "m.room.redaction" => null, _ => // Turn this on for debugging purposes false @@ -136,7 +148,7 @@ extension EventToMessage on Event { ? Message.unsupported( metadata: metadata, id: eventId, - authorId: senderId, + authorId: authorId, replyToMessageId: replyId, ) : null, diff --git a/lib/helpers/extensions/gomuks_buffer.dart b/lib/helpers/extensions/gomuks_buffer.dart index aab1f1f..88cfd5a 100644 --- a/lib/helpers/extensions/gomuks_buffer.dart +++ b/lib/helpers/extensions/gomuks_buffer.dart @@ -4,7 +4,7 @@ import "dart:typed_data"; import "package:ffi/ffi.dart"; import "package:nexus/src/third_party/gomuks.g.dart"; -extension GomuksOwnedBufferToJson on GomuksOwnedBuffer { +extension GomuksOwnedBufferToX on GomuksOwnedBuffer { Uint8List toBytes() { try { if (base == nullptr || length <= 0) return Uint8List(0); @@ -14,14 +14,7 @@ extension GomuksOwnedBufferToJson on GomuksOwnedBuffer { } } - Map toJson() { - final bytes = toBytes(); - if (bytes.isEmpty) return {}; - final json = jsonDecode(utf8.decode(bytes)); - - if (json is Map?) return json ?? {}; - throw json; - } + dynamic toJson() => jsonDecode(utf8.decode(toBytes())); } extension JsonToGomuksBuffer on Map { diff --git a/lib/helpers/extensions/list_to_messages.dart b/lib/helpers/extensions/list_to_messages.dart index edddb25..725f9c3 100644 --- a/lib/helpers/extensions/list_to_messages.dart +++ b/lib/helpers/extensions/list_to_messages.dart @@ -1,10 +1,12 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:matrix/matrix.dart"; +import "package:nexus/controllers/client_controller.dart"; import "package:nexus/helpers/extensions/event_to_message.dart"; +import "package:nexus/models/event.dart"; -extension ListToMessages on List { - Future> toMessages(Room room, Timeline timeline) async => +extension ListToMessages on IList { + Future> toMessages(ClientController client) async => (await Future.wait( - map((event) => Event.fromMatrixEvent(event, room).toMessage(timeline)), - )).nonNulls.toList().reversed.toList(); + map((event) => event.toMessage(client)), + )).nonNulls.toList(); } diff --git a/lib/models/event.dart b/lib/models/event.dart index e80dff9..623116b 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -23,7 +23,7 @@ abstract class Event with _$Event { String? transactionId, String? redactedBy, String? relatesTo, - @JsonKey(name: "relates_type") String? relationType, + String? relationType, String? decryptionError, String? sendError, @Default(IMap.empty()) IMap reactions, diff --git a/lib/models/get_event_request.dart b/lib/models/get_event_request.dart new file mode 100644 index 0000000..3812d50 --- /dev/null +++ b/lib/models/get_event_request.dart @@ -0,0 +1,15 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "get_event_request.freezed.dart"; +part "get_event_request.g.dart"; + +@freezed +abstract class GetEventRequest with _$GetEventRequest { + const factory GetEventRequest({ + required String roomId, + required String eventId, + @Default(false) bool unredact, + }) = _GetEventRequest; + + factory GetEventRequest.fromJson(Map json) => + _$GetEventRequestFromJson(json); +} diff --git a/lib/models/get_related_events_request.dart b/lib/models/get_related_events_request.dart new file mode 100644 index 0000000..7e2244f --- /dev/null +++ b/lib/models/get_related_events_request.dart @@ -0,0 +1,15 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "get_related_events_request.freezed.dart"; +part "get_related_events_request.g.dart"; + +@freezed +abstract class GetRelatedEventsRequest with _$GetRelatedEventsRequest { + const factory GetRelatedEventsRequest({ + required String roomId, + required String eventId, + required String relationType, + }) = _GetRelatedEventsRequest; + + factory GetRelatedEventsRequest.fromJson(Map json) => + _$GetRelatedEventsRequestFromJson(json); +} diff --git a/lib/models/login.dart b/lib/models/login.dart deleted file mode 100644 index 64b3c55..0000000 --- a/lib/models/login.dart +++ /dev/null @@ -1,14 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -part "login.freezed.dart"; -part "login.g.dart"; - -@freezed -abstract class Login with _$Login { - const factory Login({ - required String username, - required String password, - required String homeserverUrl, - }) = _Login; - - factory Login.fromJson(Map json) => _$LoginFromJson(json); -} diff --git a/lib/models/login_request.dart b/lib/models/login_request.dart new file mode 100644 index 0000000..b3704fa --- /dev/null +++ b/lib/models/login_request.dart @@ -0,0 +1,15 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "login_request.freezed.dart"; +part "login_request.g.dart"; + +@freezed +abstract class LoginRequest with _$LoginRequest { + const factory LoginRequest({ + required String username, + required String password, + required String homeserverUrl, + }) = _LoginRequest; + + factory LoginRequest.fromJson(Map json) => + _$LoginRequestFromJson(json); +} diff --git a/lib/models/profile.dart b/lib/models/profile.dart new file mode 100644 index 0000000..d92b4f6 --- /dev/null +++ b/lib/models/profile.dart @@ -0,0 +1,29 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:freezed_annotation/freezed_annotation.dart"; +part "profile.freezed.dart"; +part "profile.g.dart"; + +@freezed +abstract class Profile with _$Profile { + const factory Profile({ + String? avatarUrl, + @JsonKey(name: "displayname") String? displayName, + @JsonKey(name: "us.cloke.msc4175.tz") String? timezone, + + @Default(IList.empty()) + @JsonKey(name: "io.fsky.nyx.pronouns") + IList pronouns, + }) = _Profile; + + factory Profile.fromJson(Map json) => + _$ProfileFromJson(json); +} + +@freezed +abstract class Pronoun with _$Pronoun { + const factory Pronoun({required String language, required String summary}) = + _Pronoun; + + factory Pronoun.fromJson(Map json) => + _$PronounFromJson(json); +} diff --git a/lib/models/report.dart b/lib/models/report.dart deleted file mode 100644 index 8b35e7a..0000000 --- a/lib/models/report.dart +++ /dev/null @@ -1,14 +0,0 @@ -import "package:freezed_annotation/freezed_annotation.dart"; -part "report.freezed.dart"; -part "report.g.dart"; - -@freezed -abstract class Report with _$Report { - const factory Report({ - required String roomId, - required String eventId, - String? reason, - }) = _Report; - - factory Report.fromJson(Map json) => _$ReportFromJson(json); -} diff --git a/lib/models/report_request.dart b/lib/models/report_request.dart new file mode 100644 index 0000000..749ad60 --- /dev/null +++ b/lib/models/report_request.dart @@ -0,0 +1,15 @@ +import "package:freezed_annotation/freezed_annotation.dart"; +part "report_request.freezed.dart"; +part "report_request.g.dart"; + +@freezed +abstract class ReportRequest with _$ReportRequest { + const factory ReportRequest({ + required String roomId, + required String eventId, + String? reason, + }) = _ReportRequest; + + factory ReportRequest.fromJson(Map json) => + _$ReportRequestFromJson(json); +} diff --git a/lib/pages/chat_page.dart b/lib/pages/chat_page.dart index e1f1074..ee2f4d0 100644 --- a/lib/pages/chat_page.dart +++ b/lib/pages/chat_page.dart @@ -1,12 +1,13 @@ import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/widgets/chat_page/sidebar.dart"; import "package:nexus/widgets/chat_page/room_chat.dart"; -class ChatPage extends StatelessWidget { +class ChatPage extends ConsumerWidget { const ChatPage({super.key}); @override - Widget build(BuildContext context) => LayoutBuilder( + Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (context, constraints) { final isDesktop = constraints.maxWidth > 650; final showMembersByDefault = constraints.maxWidth > 1000; diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 371ad66..83d1298 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -5,7 +5,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/models/homeserver.dart"; -import "package:nexus/models/login.dart"; +import "package:nexus/models/login_request.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/loading.dart"; @@ -174,7 +174,7 @@ class LoginPage extends HookConsumerWidget { onPressed: () async { isLoading.value = true; final succeeded = await client.login( - Login( + LoginRequest( username: username.text, password: password.text, homeserverUrl: homeserver.value!, diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index 10b02fe..876f065 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -44,9 +44,9 @@ class ChatBox extends HookConsumerWidget { } void send() { - if (controller.value.text.trim().isEmpty) return; + if (controller.value.text.trim().isEmpty || room.metadata == null) return; ref - .watch(RoomChatController.provider(room).notifier) + .watch(RoomChatController.provider(room.metadata!.id).notifier) .send( controller.value.formattedText, relation: relatedMessage, diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 18baf2a..c124c24 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -18,7 +18,7 @@ import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/extensions/show_context_menu.dart"; import "package:nexus/models/relation_type.dart"; -import "package:nexus/models/report.dart"; +import "package:nexus/models/report_request.dart"; import "package:nexus/widgets/chat_page/chat_box.dart"; import "package:nexus/widgets/chat_page/html/html.dart"; import "package:nexus/widgets/chat_page/room_appbar.dart"; @@ -43,12 +43,13 @@ class RoomChat extends HookConsumerWidget { final replyToMessage = useState(null); final memberListOpened = useState(showMembersByDefault); final relationType = useState(RelationType.reply); - final theme = Theme.of(context); - final danger = theme.colorScheme.error; final room = ref.watch(SelectedRoomController.provider); final userId = ref.watch(ClientStateController.provider)?.userId; - if (room == null || userId == null) { + final theme = Theme.of(context); + final danger = theme.colorScheme.error; + + if (room == null || userId == null || room.metadata?.id == null) { return Center( child: Text( "Nothing to see here...", @@ -56,7 +57,8 @@ class RoomChat extends HookConsumerWidget { ), ); } - final controllerProvider = RoomChatController.provider(room); + + final controllerProvider = RoomChatController.provider(room.metadata!.id); final notifier = ref.watch(controllerProvider.notifier); List getMessageOptions(Message message) { @@ -158,7 +160,7 @@ class RoomChat extends HookConsumerWidget { onPressed: () { if (room.metadata == null) return; client.reportEvent( - Report( + ReportRequest( roomId: room.metadata!.id, eventId: message.id, reason: reasonController.text.isEmpty