From f9f4a4b48e490f0353db33e7e40bd69d06899c6a Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 11:20:17 -0500 Subject: [PATCH 01/50] working edits --- lib/controllers/room_chat_controller.dart | 16 +++++++++++++--- lib/helpers/extensions/event_to_message.dart | 1 - lib/widgets/chat_page/chat_box.dart | 6 +++++- lib/widgets/chat_page/room_chat.dart | 1 + 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index 1a1be39..8811c4f 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -40,7 +40,16 @@ class RoomChatController extends AsyncNotifier { if (oldMessage == null || message == null) return; return await updateMessage( oldMessage, - message.copyWith(id: oldMessage.id), + 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) { @@ -119,8 +128,9 @@ class RoomChatController extends AsyncNotifier { await room.sendTextEvent( taggedMessage, editEventId: relationType == RelationType.edit ? relation?.id : null, - inReplyTo: (relationType == RelationType.reply && relation != null) - ? await room.getEventById(relation.id) + displayPendingEvent: relationType != RelationType.edit, + inReplyTo: (relationType == RelationType.reply) + ? await room.getEventById(relation!.id) : null, ); } diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index c003180..055fcc8 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -9,7 +9,6 @@ extension EventToMessage on Event { 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); diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index 016e3a7..beee0d2 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -35,9 +35,13 @@ class ChatBox extends HookConsumerWidget { relatedMessage is TextMessage && controller.value.text.isEmpty) { final text = (relatedMessage as TextMessage).text; - controller.value.text = relatedMessage?.replyToMessageId == null + final splitText = relatedMessage?.replyToMessageId == null ? text : text.split("\n\n").sublist(1).join("\n\n"); + final notEmpty = splitText.isEmpty ? text : splitText; + controller.value.text = notEmpty.startsWith("* ") + ? notEmpty.substring(2) + : notEmpty; } void send() { diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 0190e64..9514c88 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -67,6 +67,7 @@ class RoomChat extends HookConsumerWidget { title: Text("Reply"), ), ), + // Should check if is state event (has state_key), if so, don't show edit option if (message.authorId == room.roomData.client.userID) PopupMenuItem( onTap: () { From a429ef9b25be146f1bc488fa0e9bbe5c55fa49e0 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 11:23:31 -0500 Subject: [PATCH 02/50] only allow editing text messages --- lib/widgets/chat_page/room_chat.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 9514c88..81080cf 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -68,7 +68,8 @@ class RoomChat extends HookConsumerWidget { ), ), // Should check if is state event (has state_key), if so, don't show edit option - if (message.authorId == room.roomData.client.userID) + if (message is TextMessage && + message.authorId == room.roomData.client.userID) PopupMenuItem( onTap: () { replyToMessage.value = message; From 50a6eb7c92d56864cf4c7a4ae1e90d6086d2e335 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 11:24:20 -0500 Subject: [PATCH 03/50] mark edits as complete in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 670e20e..359f7fc 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] History loading - [x] Backwards - [ ] Forwards - - [ ] Editing + - [x] Editing - [x] Deleting - [ ] Reactions: Waiting on https://github.com/flyerhq/flutter_chat_ui/pull/838 - [ ] Pins From cb5846600b89e1647778b1b421275f9093d1f52c Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 12:08:34 -0500 Subject: [PATCH 04/50] fix some bugs i think ? --- lib/controllers/room_chat_controller.dart | 5 ++- lib/widgets/chat_page/chat_box.dart | 6 +--- lib/widgets/chat_page/room_chat.dart | 26 +++++++++----- pubspec.lock | 36 +++++++++---------- .../flutter/generated_plugin_registrant.cc | 3 ++ windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index 8811c4f..b9ea7b0 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -128,9 +128,8 @@ class RoomChatController extends AsyncNotifier { await room.sendTextEvent( taggedMessage, editEventId: relationType == RelationType.edit ? relation?.id : null, - displayPendingEvent: relationType != RelationType.edit, - inReplyTo: (relationType == RelationType.reply) - ? await room.getEventById(relation!.id) + inReplyTo: (relationType == RelationType.reply && relation != null) + ? await room.getEventById(relation.id) : null, ); } diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index beee0d2..23331af 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -123,11 +123,7 @@ class ChatBox extends HookConsumerWidget { triggerCharacter.value = newTriggerCharacter; query.value = newQuery; }, - triggerCharacterAndStyles: { - "@": style, - "#": style, - ":": style, - }, + triggerCharacterAndStyles: {"@": style, "#": style}, builder: (context, key) => TextFormField( enabled: room.canSendDefaultMessages, maxLines: 12, diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index 81080cf..b73afdb 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -201,12 +201,15 @@ class RoomChat extends HookConsumerWidget { ( context, message, { - required details, required index, - }) => context.showContextMenu( - globalPosition: details.globalPosition, - children: getMessageOptions(message), - ), + TapUpDetails? details, + }) => details?.globalPosition == null + ? null + : context.showContextMenu( + globalPosition: + details!.globalPosition, + children: getMessageOptions(message), + ), onMessageLongPress: ( context, @@ -246,11 +249,18 @@ class RoomChat extends HookConsumerWidget { as String) .replaceAllMapped( RegExp( - regexLink, + "(]*>.*?<\\/a>)|(\\bhttps?:\\/\\/[^\\s<]+)", caseSensitive: false, ), - (m) => - "${m.group(0)!}", + (m) { + // If it's already an tag, leave it unchanged + if (m.group(1) != null) + return m.group(1)!; + + // Otherwise, wrap the bare URL + final url = m.group(2)!; + return "$url"; + }, ) .replaceAll("\n", "
") + ((message.editedAt != null) diff --git a/pubspec.lock b/pubspec.lock index 871bb6f..e3a9f82 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -197,10 +197,10 @@ packages: dependency: "direct main" description: name: clipboard - sha256: "619f4e9e946cfd637ac994f49af356bb590ab88b0c4aded03204ee566fd69d9e" + sha256: c668fd6ce6715b5054766a3217c88bcf56f993c373ec95121a49c9415a67aa99 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" clock: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" collection: dependency: "direct main" description: @@ -301,10 +301,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: "91f2a81e9f0abb4b9f3bb529f78b6227ce6050300d1ae5b1e2c69c66c7a566d8" + sha256: e466d17856197cf9bce7ca03804d784fddab809db7bda787f3d2799ac89faadd url: "https://pub.dev" source: hosted - version: "1.0.0+8.4.0" + version: "1.0.0+9.0.0" dart_style: dependency: transitive description: @@ -451,10 +451,10 @@ packages: description: path: "packages/flutter_chat_ui" ref: HEAD - resolved-ref: "6cfbadbf364251dd3c6a986e20c9d97636ad3412" + resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git - version: "2.9.1" + version: "2.11.1" flutter_hooks: dependency: "direct main" description: @@ -476,10 +476,10 @@ packages: description: path: "packages/flutter_link_previewer" ref: HEAD - resolved-ref: "6cfbadbf364251dd3c6a986e20c9d97636ad3412" + resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git - version: "4.1.2" + version: "4.2.0" flutter_lints: dependency: "direct dev" description: @@ -596,18 +596,18 @@ packages: description: path: "packages/flyer_chat_text_message" ref: HEAD - resolved-ref: "6cfbadbf364251dd3c6a986e20c9d97636ad3412" + resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git - version: "2.5.2" + version: "2.6.0" freezed: dependency: "direct dev" description: name: freezed - sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" + sha256: "03dd9b7423ff0e31b7e01b2204593e5e1ac5ee553b6ea9d8184dff4a26b9fb07" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.4" freezed_annotation: dependency: "direct main" description: @@ -1164,10 +1164,10 @@ packages: dependency: transitive description: name: scrollview_observer - sha256: c2f713509f18f88f637b2084b47a90c91fb1ef066d5d82d2cf3194d8509dc6ab + sha256: "6e40ced415145c449a691d892157a3b854b751f024aed20d9aebda04c21444a3" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" sdp_transform: dependency: transitive description: @@ -1401,10 +1401,10 @@ packages: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "9faa2fedc5385ef238ce772589f7718c24cdddd27419b609bb9c6f703ea27988" + sha256: "8d7b8749a516cbf6e9057f9b480b716ad14fc4f3d3873ca6938919cc626d9025" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7+1" sqlite3: dependency: transitive description: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 9d7af86..cb2109e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -16,6 +17,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + ClipboardPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ClipboardPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index dcf3309..7e7687e 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + clipboard dynamic_system_colors file_selector_windows screen_retriever_windows From 45ae72ed2af05cbd5371fa460536dd3f01866228 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 12:23:57 -0500 Subject: [PATCH 05/50] Disable unsupported messages even on debug builds We could make a setting for this, maybe! --- lib/helpers/extensions/event_to_message.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index 055fcc8..59992d8 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -120,7 +120,8 @@ extension EventToMessage on Event { ), EventTypes.Redaction => null, _ => - kDebugMode + // Turn this on for debugging purposes + false ? Message.unsupported( metadata: metadata, id: eventId, From e8a8bb3b4f7ce2540e3cb787e4780c174ae4c38f Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 13:27:11 -0500 Subject: [PATCH 06/50] Fix warnings --- lib/helpers/extensions/event_to_message.dart | 1 - lib/widgets/chat_page/room_chat.dart | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index 59992d8..bef1797 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -1,5 +1,4 @@ import "package:collection/collection.dart"; -import "package:flutter/foundation.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:matrix/matrix.dart"; diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index b73afdb..d4827be 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -254,8 +254,10 @@ class RoomChat extends HookConsumerWidget { ), (m) { // If it's already an tag, leave it unchanged - if (m.group(1) != null) + if (m.group(1) != + null) { return m.group(1)!; + } // Otherwise, wrap the bare URL final url = m.group(2)!; From 0f563455ffda25ce9667e736d9a9b395a450338a Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 13:39:51 -0500 Subject: [PATCH 07/50] Mark as read button --- README.md | 2 +- lib/controllers/spaces_controller.dart | 2 +- lib/helpers/extensions/room_to_children.dart | 4 ++-- lib/widgets/chat_page/room_menu.dart | 17 +++++++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 359f7fc..f27aeff 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Rooms / Spaces - [x] Displaying and choosing - [x] Reading, showing unread - - [ ] Mark as read button on rooms and spaces + - [x] Mark as read button on rooms and spaces - [ ] Searching - [ ] Creating (Rooms, Spaces, and DMs) - [ ] Joining diff --git a/lib/controllers/spaces_controller.dart b/lib/controllers/spaces_controller.dart index 3501de6..408dc00 100644 --- a/lib/controllers/spaces_controller.dart +++ b/lib/controllers/spaces_controller.dart @@ -64,7 +64,7 @@ class SpacesController extends AsyncNotifier> { avatar: space.avatar, id: space.roomData.id, roomData: space.roomData, - children: IList(await space.roomData.getAllChildren(client)), + children: IList(await space.roomData.getAllChildren()), ), ), )), diff --git a/lib/helpers/extensions/room_to_children.dart b/lib/helpers/extensions/room_to_children.dart index afdc99e..d115f9a 100644 --- a/lib/helpers/extensions/room_to_children.dart +++ b/lib/helpers/extensions/room_to_children.dart @@ -5,7 +5,7 @@ import "package:nexus/helpers/extensions/get_full_room.dart"; import "package:nexus/models/full_room.dart"; extension RoomToChildren on Room { - Future> getAllChildren(Client client) async { + Future> getAllChildren() async { final direct = await Future.wait( spaceChildren .map( @@ -19,7 +19,7 @@ extension RoomToChildren on Room { return (await Future.wait( direct.map( (child) async => child.roomData.isSpace - ? await child.roomData.getAllChildren(client) + ? await child.roomData.getAllChildren() : [child], ), )).expand((list) => list).toIList(); diff --git a/lib/widgets/chat_page/room_menu.dart b/lib/widgets/chat_page/room_menu.dart index c5fa322..8169095 100644 --- a/lib/widgets/chat_page/room_menu.dart +++ b/lib/widgets/chat_page/room_menu.dart @@ -2,6 +2,7 @@ import "package:clipboard/clipboard.dart"; import "package:flutter/material.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:matrix/matrix.dart"; +import "package:nexus/helpers/extensions/room_to_children.dart"; import "package:nexus/widgets/form_text_input.dart"; class RoomMenu extends StatelessWidget { @@ -12,6 +13,15 @@ class RoomMenu extends StatelessWidget { Widget build(BuildContext context) { final danger = Theme.of(context).colorScheme.error; + void markRead(String roomId) async { + for (final child in await room.getAllChildren()) { + await child.roomData.setReadMarker( + child.roomData.lastEvent?.eventId, + mRead: child.roomData.lastEvent?.eventId, + ); + } + } + return PopupMenuButton( itemBuilder: (_) => [ PopupMenuItem( @@ -21,6 +31,13 @@ class RoomMenu extends StatelessWidget { }, child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")), ), + PopupMenuItem( + onTap: () => markRead(room.id), + child: ListTile( + leading: Icon(Icons.check), + title: Text("Mark as Read"), + ), + ), PopupMenuItem( onTap: () => showDialog( context: context, From 168546ae45230e58581c63401206e9bc6b17787d Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 13:59:16 -0500 Subject: [PATCH 08/50] init vod properly --- lib/controllers/client_controller.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index 1a88526..1826697 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -2,7 +2,8 @@ import "dart:convert"; import "dart:io"; import "package:flutter/foundation.dart"; import "package:nexus/controllers/database_controller.dart"; -import "package:flutter_vodozemac/flutter_vodozemac.dart"; +import "package:vodozemac/vodozemac.dart" as vod; +import "package:flutter_vodozemac/flutter_vodozemac.dart" as fl_vod; import "package:matrix/matrix.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/secure_storage_controller.dart"; @@ -18,6 +19,7 @@ class ClientController extends AsyncNotifier { @override Future build() async { + if (!vod.isInitialized()) fl_vod.init(); final client = Client( "nexus", logLevel: kReleaseMode ? Level.warning : Level.verbose, @@ -29,7 +31,7 @@ class ClientController extends AsyncNotifier { ), nativeImplementations: NativeImplementationsIsolate( compute, - vodozemacInit: init, + vodozemacInit: fl_vod.init, ), ); From c4516c312ae43511aec58492ad0539a462523334 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 14:00:32 -0500 Subject: [PATCH 09/50] update build instructions --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f27aeff..d4210d1 100644 --- a/README.md +++ b/README.md @@ -103,18 +103,43 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S ## Development -Fork and clone the project, then: +First, clone and open the repo: + +```sh +git clone https://git.federated.nexus/Henry-Hiles/nexus +cd nexus +``` + +### Prerequisites + +#### Linux - With Nix: Either use direnv, or `nix flake develop` - Without Nix: Install Flutter, Rust, the libsecret dev package for your distro (must be in `PKG_CONFIG_PATH`), and sqlite (must be in `LD_LIBRARY_PATH`). +#### Windows / MacOS + +I don't really know... Probably just install Rust, Flutter, and Git. + +### + +Get dependencies: + +```sh +flutter pub get +``` + Build generated files, and watch for new changes: ```sh flutter pub run build_runner watch --delete-conflicting-outputs ``` -Run `flutter run` to run the app. +Run the app: + +```sh +flutter run +``` ## Community From 1539235d7d8b14811b23b1d0e92914a68a1397dd Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 16:11:21 -0500 Subject: [PATCH 10/50] Partially working encryption --- lib/controllers/client_controller.dart | 8 ++++++++ pubspec.lock | 9 +++++---- pubspec.yaml | 5 +++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index 1826697..ad25a30 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -1,6 +1,7 @@ import "dart:convert"; import "dart:io"; import "package:flutter/foundation.dart"; +import "package:matrix/encryption.dart"; import "package:nexus/controllers/database_controller.dart"; import "package:vodozemac/vodozemac.dart" as vod; import "package:flutter_vodozemac/flutter_vodozemac.dart" as fl_vod; @@ -25,6 +26,7 @@ class ClientController extends AsyncNotifier { logLevel: kReleaseMode ? Level.warning : Level.verbose, importantStateEvents: {"im.ponies.room_emotes"}, supportedLoginTypes: {AuthenticationTypes.password}, + verificationMethods: {KeyVerificationMethod.emoji}, database: await MatrixSdkDatabase.init( "nexus", database: await ref.watch(DatabaseController.provider.future), @@ -52,6 +54,12 @@ class ClientController extends AsyncNotifier { ); } + if (client.userID != null) { + // client.encryption?.keyVerificationManager.addRequest( + // KeyVerification(encryption: client.encryption!, userId: client.userID!), + // ); + } + return client; } diff --git a/pubspec.lock b/pubspec.lock index e3a9f82..69a7134 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1640,10 +1640,11 @@ packages: vodozemac: dependency: "direct main" description: - name: vodozemac - sha256: "39144e20740807731871c9248d811ed5a037b21d0aa9ffcfa630954de74139d9" - url: "https://pub.dev" - source: hosted + path: dart + ref: "krille/use-specced-olm-session-config" + resolved-ref: "8770e0555b1bb692e3e1a43a7726b27eae285b20" + url: "https://github.com/famedly/dart-vodozemac" + source: git version: "0.4.0" watcher: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 9551407..32f2cbf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,11 @@ environment: sdk: "^3.9.2" dependency_overrides: + vodozemac: + git: + url: https://github.com/famedly/dart-vodozemac + ref: krille/use-specced-olm-session-config + path: dart analyzer: ^8.4.0 source_gen: ^4.0.2 From 3fd0d5f4614f1eef8ed534d6b1a13853c67153ca Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 16:31:51 -0500 Subject: [PATCH 11/50] Use timelines, encryption now works, except not fetching keys from old devices --- lib/controllers/events_controller.dart | 26 ++++++----------------- lib/controllers/from_controller.dart | 15 ------------- lib/controllers/room_chat_controller.dart | 24 ++++++++++++++------- lib/main.dart | 1 + 4 files changed, 24 insertions(+), 42 deletions(-) delete mode 100644 lib/controllers/from_controller.dart diff --git a/lib/controllers/events_controller.dart b/lib/controllers/events_controller.dart index 37b9ff2..e7a192d 100644 --- a/lib/controllers/events_controller.dart +++ b/lib/controllers/events_controller.dart @@ -1,30 +1,18 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:matrix/matrix.dart"; -import "package:nexus/controllers/from_controller.dart"; -class EventsController extends AsyncNotifier { +class EventsController extends AsyncNotifier { EventsController(this.room); final Room room; @override - Future build({String? from}) async { - final response = await room.client.getRoomEvents( - room.id, - Direction.b, - from: from, - limit: 32, - ); - if (ref.mounted) { - ref.watch(FromController.provider(room).notifier).set(response.end); - } - return response; + Future build({String? from}) => room.getTimeline(); + + Future prev() async { + final timeline = await future; + await timeline.requestHistory(); } - Future prev() async => - build(from: ref.read(FromController.provider(room))); - static final provider = AsyncNotifierProvider.autoDispose - .family( - EventsController.new, - ); + .family(EventsController.new); } diff --git a/lib/controllers/from_controller.dart b/lib/controllers/from_controller.dart deleted file mode 100644 index 54c850a..0000000 --- a/lib/controllers/from_controller.dart +++ /dev/null @@ -1,15 +0,0 @@ -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:matrix/matrix.dart"; - -class FromController extends Notifier { - FromController(_); - @override - String? build() => null; - - void set(String? value) => state = value; - - static final provider = - NotifierProvider.family( - FromController.new, - ); -} diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index b9ea7b0..e9bd7ba 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -60,7 +60,7 @@ class RoomChatController extends AsyncNotifier { ); return InMemoryChatController( - messages: await response.chunk.toMessages(room), + messages: await response.events.toMessages(room), ); } @@ -85,14 +85,22 @@ class RoomChatController extends AsyncNotifier { } Future loadOlder() async { + final currentEvents = await future; + await ref.watch(EventsController.provider(room).notifier).prev(); + final newEvents = await ref.watch(EventsController.provider(room).future); + final controller = await future; - final response = await ref - .watch(EventsController.provider(room).notifier) - .prev(); - - final messages = await response.chunk.toMessages(room); - - await controller.insertAllMessages(messages, index: 0); + await controller.insertAllMessages( + await newEvents.events + .where( + (event) => !currentEvents.messages.any( + (existingEvent) => existingEvent.id == event.eventId, + ), + ) + .toList() + .toMessages(room), + index: 0, + ); ref.notifyListeners(); } diff --git a/lib/main.dart b/lib/main.dart index 8cf4365..bf65f75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,6 +34,7 @@ New Value: ${newValue is AsyncData ? newValue.value : newValue} void showError(Object error, [StackTrace? stackTrace]) { if (error.toString().contains("DioException")) return; if (error.toString().contains("UTF-16")) return; + if (error.toString().contains("HTTP request failed")) return; if (error.toString().contains("Invalid image data")) return; debugPrintStack(stackTrace: stackTrace, label: error.toString()); From 68fba2c412567e057d15abedbe0a3d0865d30a71 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 17:52:57 -0500 Subject: [PATCH 12/50] bump --- README.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4210d1..902e5ea 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ cd nexus #### Windows / MacOS -I don't really know... Probably just install Rust, Flutter, and Git. +Install Rust, Flutter, and Git. ### diff --git a/pubspec.yaml b/pubspec.yaml index 32f2cbf..dbbe4ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.8 + clipboard: ^3.0.10 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 From 2dcd81a5c4401705203da8ec1282a2e4373b1b96 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 18:06:12 -0500 Subject: [PATCH 13/50] upgrade clipboard --- flake.lock | 18 +++++++++--------- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/flake.lock b/flake.lock index b627cd3..7826732 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1759362264, - "narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "758cf7296bee11f1706a574c77d072b8a7baa881", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1759381078, - "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "lastModified": 1767640445, + "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1754788789, - "narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=", + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "a73b9c743612e4244d865a2fdee11865283c04e6", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", "type": "github" }, "original": { diff --git a/pubspec.lock b/pubspec.lock index 69a7134..3bc35af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1473,26 +1473,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" thumbhash: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dbbe4ef..32f2cbf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.10 + clipboard: ^3.0.8 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 From 94d40d2416093ba570a7d0508887131b56adf012 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 19:52:02 -0500 Subject: [PATCH 14/50] add workflow --- .github/workflows/main.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6f5b41c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,33 @@ +name: "Build Windows EXE" + +on: + workflow_dispatch: + +jobs: + build-windows: + runs-on: "windows-latest" + + steps: + - name: "Checkout repository" + uses: "actions/checkout@v4" + + - name: "Set up Flutter" + uses: "subosito/flutter-action@v2" + + - name: "Set up Rust" + uses: "dtolnay/rust-toolchain@stable" + with: + targets: "x86_64-pc-windows-msvc" + + - name: "Install Flutter dependencies" + run: "flutter pub get" + + - name: "Build Windows EXE" + run: | + flutter build windows --release + + - name: "Upload Windows build" + uses: "actions/upload-artifact@v4" + with: + name: "windows-exe" + path: "build/windows/x64/runner/Release/" \ No newline at end of file From be860e5d7d2667bc6c5a299e2403f39b953fd310 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 19:52:32 -0500 Subject: [PATCH 15/50] fix build --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f5b41c..0b1d25a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,7 @@ jobs: - name: "Build Windows EXE" run: | + flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release - name: "Upload Windows build" From 03f3634d9dccdc6c4323a4c5c14f4615f33762d6 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Tue, 6 Jan 2026 20:08:41 -0500 Subject: [PATCH 16/50] bump clipboard --- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 3bc35af..69a7134 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1473,26 +1473,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.11" thumbhash: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 32f2cbf..dbbe4ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.8 + clipboard: ^3.0.10 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 From fd34669c5fefc943a5adeccd0846d836f87512b4 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 09:24:43 -0500 Subject: [PATCH 17/50] downgrade clipboard --- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 69a7134..3bc35af 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1473,26 +1473,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" thumbhash: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dbbe4ef..988bde3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.10 + clipboard: ^3.0.9 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 From 378355494026f2a76fb73ccf7d21745eaac195a3 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 13:42:58 -0500 Subject: [PATCH 18/50] ignore dead code for line --- lib/helpers/extensions/event_to_message.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index bef1797..28d9f4f 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -121,6 +121,7 @@ extension EventToMessage on Event { _ => // Turn this on for debugging purposes false + // ignore: dead_code ? Message.unsupported( metadata: metadata, id: eventId, From 0b5641e398c94ccfb7e570034792f905ab8bcf77 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 13:54:43 -0500 Subject: [PATCH 19/50] use system font --- lib/helpers/extensions/scheme_to_theme.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/helpers/extensions/scheme_to_theme.dart b/lib/helpers/extensions/scheme_to_theme.dart index e238cf9..aff5d52 100644 --- a/lib/helpers/extensions/scheme_to_theme.dart +++ b/lib/helpers/extensions/scheme_to_theme.dart @@ -7,6 +7,10 @@ extension SchemeToTheme on ColorScheme { titleSpacing: 0, backgroundColor: surfaceContainerLow, ), + textTheme: ThemeData( + fontFamilyFallback: ["sans"], + brightness: brightness, + ).textTheme, inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), ), From 6298758686048d0cb90b5cfcb6cf01f673356a25 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 14:13:42 -0500 Subject: [PATCH 20/50] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 902e5ea..ce5f5c3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Platform Support - [x] Linux - - [x] Windows (untested, if you are interested in helping to test, open an issue) + - [ ] Windows: Waiting on https://github.com/samuelezedi/flutter_clipboard/issues/21 - [ ] MacOS - [ ] Android - [ ] iOS From 257ebdd5b5de8d5ec300632ba57e88d36a6b6eae Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 15:27:00 -0500 Subject: [PATCH 21/50] bump clipboard --- pubspec.lock | 20 ++++++++++---------- pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 3bc35af..88a2ab6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -197,10 +197,10 @@ packages: dependency: "direct main" description: name: clipboard - sha256: c668fd6ce6715b5054766a3217c88bcf56f993c373ec95121a49c9415a67aa99 + sha256: e95ef304eaeadc782c24cde89d219da9360a89364c9e30e9ab888d0713f0063a url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.13" clock: dependency: transitive description: @@ -908,10 +908,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1473,26 +1473,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.11" thumbhash: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 988bde3..24079a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,7 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.9 + clipboard: ^3.0.13 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 From 060d154665b4c1894a56be2e958c14a154a62fba Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 15:56:50 -0500 Subject: [PATCH 22/50] make badges larger --- lib/widgets/avatar_or_hash.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart index 41bd002..a077810 100644 --- a/lib/widgets/avatar_or_hash.dart +++ b/lib/widgets/avatar_or_hash.dart @@ -30,8 +30,8 @@ class AvatarOrHash extends StatelessWidget { child: Center( child: Badge( isLabelVisible: hasBadge, - smallSize: 8, - backgroundColor: Theme.of(context).colorScheme.onPrimaryContainer, + smallSize: 12, + backgroundColor: Theme.of(context).colorScheme.primary, child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(4)), child: SizedBox( From c44c1635e158a8ae8ec62d167b12035cef3e0d40 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 16:24:23 -0500 Subject: [PATCH 23/50] add ls's --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b1d25a..718ee7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,9 @@ jobs: run: | flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release + ls build\windows + ls build\windows\x64\runner\Release + ls build\windows\runner - name: "Upload Windows build" uses: "actions/upload-artifact@v4" From 0c118c5b6796c73f675fce5540c377bc14da52c2 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 7 Jan 2026 16:35:55 -0500 Subject: [PATCH 24/50] upload the right file --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 718ee7d..46e05cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,12 +26,9 @@ jobs: run: | flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release - ls build\windows - ls build\windows\x64\runner\Release - ls build\windows\runner - name: "Upload Windows build" uses: "actions/upload-artifact@v4" with: name: "windows-exe" - path: "build/windows/x64/runner/Release/" \ No newline at end of file + path: "build/windows/x64/runner/Release/nexus.exe" \ No newline at end of file From c048aee833a5a90ee92a82b08844eff90f1ee6d8 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 10:02:26 -0500 Subject: [PATCH 25/50] remove clipboard stuff --- lib/widgets/chat_page/room_menu.dart | 4 ++-- pubspec.lock | 24 +++++++------------ pubspec.yaml | 1 - .../flutter/generated_plugin_registrant.cc | 3 --- windows/flutter/generated_plugins.cmake | 1 - 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/lib/widgets/chat_page/room_menu.dart b/lib/widgets/chat_page/room_menu.dart index 8169095..9dee21d 100644 --- a/lib/widgets/chat_page/room_menu.dart +++ b/lib/widgets/chat_page/room_menu.dart @@ -1,5 +1,5 @@ -import "package:clipboard/clipboard.dart"; import "package:flutter/material.dart"; +import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:matrix/matrix.dart"; import "package:nexus/helpers/extensions/room_to_children.dart"; @@ -27,7 +27,7 @@ class RoomMenu extends StatelessWidget { PopupMenuItem( onTap: () async { final link = await room.matrixToInviteLink(); - await FlutterClipboard.copy(link.toString()); + await Clipboard.setData(ClipboardData(text: link.toString())); }, child: ListTile(leading: Icon(Icons.link), title: Text("Copy Link")), ), diff --git a/pubspec.lock b/pubspec.lock index 88a2ab6..1c5df6c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -193,14 +193,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" - clipboard: - dependency: "direct main" - description: - name: clipboard - sha256: e95ef304eaeadc782c24cde89d219da9360a89364c9e30e9ab888d0713f0063a - url: "https://pub.dev" - source: hosted - version: "3.0.13" clock: dependency: transitive description: @@ -908,10 +900,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1473,26 +1465,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" thumbhash: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 24079a6..29a9055 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,7 +68,6 @@ dependencies: simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 - clipboard: ^3.0.13 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index cb2109e..9d7af86 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,7 +6,6 @@ #include "generated_plugin_registrant.h" -#include #include #include #include @@ -17,8 +16,6 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { - ClipboardPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ClipboardPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7e7687e..dcf3309 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - clipboard dynamic_system_colors file_selector_windows screen_retriever_windows From 9c99aabf293448fb30aa190696289dc07ff5005a Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 11:38:29 -0500 Subject: [PATCH 26/50] add installer --- .github/workflows/main.yml | 18 ++++++++++++------ installer.iss | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 installer.iss diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46e05cd..d9fce5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: "Build Windows EXE" +name: "Build Windows Installer" on: workflow_dispatch: @@ -20,15 +20,21 @@ jobs: targets: "x86_64-pc-windows-msvc" - name: "Install Flutter dependencies" - run: "flutter pub get" + run: flutter pub get - - name: "Build Windows EXE" + - name: "Run build_runner & build Windows EXE" run: | flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release - - name: "Upload Windows build" + - name: "Install Inno Setup" + run: choco install innosetup -y + + - name: "Build Inno Setup installer" + run: iscc installer.iss + + - name: "Upload installer artifact" uses: "actions/upload-artifact@v4" with: - name: "windows-exe" - path: "build/windows/x64/runner/Release/nexus.exe" \ No newline at end of file + name: "windows-installer" + path: "dist/*.exe" diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..a3e7990 --- /dev/null +++ b/installer.iss @@ -0,0 +1 @@ +Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion From a3b1bc8e0efccdb31cde368f32d9ecc6be18fdc0 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 11:51:55 -0500 Subject: [PATCH 27/50] Fix installer --- installer.iss | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/installer.iss b/installer.iss index a3e7990..96c48e3 100644 --- a/installer.iss +++ b/installer.iss @@ -1 +1,17 @@ +[Setup] +AppName=Nexus +AppVersion=1.0.0 +DefaultDirName={pf}\Nexus +DefaultGroupName=Nexus +OutputDir=dist +OutputBaseFilename=Nexus-Setup +Compression=lzma +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 + +[Files] Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion + +[Icons] +Name: "{group}\Nexus"; Filename: "{app}\nexus.exe" +Name: "{commondesktop}\Nexus"; Filename: "{app}\nexus.exe" \ No newline at end of file From 02f3a31db1e37ca181455058d95225ddd81b9aa1 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 12:05:53 -0500 Subject: [PATCH 28/50] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce5f5c3..51ce41d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Leaving - [x] Subspaces - [x] Messages + - [x] Encryption + - [ ] Restoring crypto identity from passphrase/key or verification - [x] Sending - [x] Plain text - [x] HTML/Markdown @@ -48,7 +50,6 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Rooms - [ ] Custom emojis/stickers - [ ] GIFs, maybe through Tenor or something - - [ ] Encrypted messages - [x] Recieving - [x] Plain text - [x] HTML @@ -66,7 +67,6 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Matrix URIs - [x] Matrix.to links - [x] Custom emojis/stickers - - [ ] Encrypted messages - [x] History loading - [x] Backwards - [ ] Forwards From a5aee2a701aa83d319f133f21fa511470d83f0ce Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 12:06:34 -0500 Subject: [PATCH 29/50] Add windows to readme again --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51ce41d..2f1e8e1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Platform Support - [x] Linux - - [ ] Windows: Waiting on https://github.com/samuelezedi/flutter_clipboard/issues/21 + - [x] Windows: Untested, waiting for testers - [ ] MacOS - [ ] Android - [ ] iOS From f2cdc03798231048bcd64417136a7d8ca76c758d Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 12:07:30 -0500 Subject: [PATCH 30/50] update build instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f1e8e1..9e02259 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ cd nexus #### Windows / MacOS -Install Rust, Flutter, and Git. +I don't really know. You will need Flutter and Rust, and otherwise I guess just keep installing stuff until there aren't any errors. ### From 9efd82a52ae6a5e2353ddbd4cf0e0d7e8b90f024 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 12:14:18 -0500 Subject: [PATCH 31/50] upload specifically the exe --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d9fce5f..38ef2f3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,4 +37,4 @@ jobs: uses: "actions/upload-artifact@v4" with: name: "windows-installer" - path: "dist/*.exe" + path: "dist/Nexus-Setup.exe" From ed10ec8c0939bab01495bb5032666103f7038af1 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 12:29:49 -0500 Subject: [PATCH 32/50] auto dispose members controller (is there a reason i didnt do this before?) --- lib/controllers/members_controller.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/members_controller.dart b/lib/controllers/members_controller.dart index fae5433..df15c1c 100644 --- a/lib/controllers/members_controller.dart +++ b/lib/controllers/members_controller.dart @@ -15,8 +15,8 @@ class MembersController extends AsyncNotifier> { [], ); - static final provider = - AsyncNotifierProvider.family, Room>( + static final provider = AsyncNotifierProvider.family + .autoDispose, Room>( MembersController.new, ); } From 2d382fa63d5834935a1ce8077e8a5aab9de13e97 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 13:24:08 -0500 Subject: [PATCH 33/50] upload portable too --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38ef2f3..c82c0a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,12 @@ jobs: flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release + name: "Upload exe zip" + uses: "actions/upload-artifact@v4" + with: + name: "windows-portable" + path: "build/windows/x64/runner/Release/" + - name: "Install Inno Setup" run: choco install innosetup -y From 88449643740c83fe49d27a2e3ec3f7e93cbaa13b Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 13:24:56 -0500 Subject: [PATCH 34/50] fix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c82c0a5..75815b8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: flutter pub run build_runner build --delete-conflicting-outputs flutter build windows --release - name: "Upload exe zip" + - name: "Upload exe zip" uses: "actions/upload-artifact@v4" with: name: "windows-portable" From e3d051767c2d0d5c93945cf4c3ab87d126960227 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 18:51:20 -0500 Subject: [PATCH 35/50] use flutter secure storage not simple secure storage --- .../secure_storage_controller.dart | 29 ++-- linux/flutter/generated_plugin_registrant.cc | 12 +- linux/flutter/generated_plugins.cmake | 3 +- pubspec.lock | 128 +++++++----------- pubspec.yaml | 2 +- .../flutter/generated_plugin_registrant.cc | 9 +- windows/flutter/generated_plugins.cmake | 3 +- 7 files changed, 69 insertions(+), 117 deletions(-) diff --git a/lib/controllers/secure_storage_controller.dart b/lib/controllers/secure_storage_controller.dart index 8a579f5..4a5781b 100644 --- a/lib/controllers/secure_storage_controller.dart +++ b/lib/controllers/secure_storage_controller.dart @@ -1,26 +1,19 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:simple_secure_storage/simple_secure_storage.dart"; +import "package:flutter_secure_storage/flutter_secure_storage.dart"; -class SecureStorageController extends AsyncNotifier { +class SecureStorageController extends Notifier { @override - Future build() => SimpleSecureStorage.initialize(); + FlutterSecureStorage build() => FlutterSecureStorage(); - Future get(String key) async { - await future; - return SimpleSecureStorage.read(key); - } + Future get(String key) => state.read(key: key); - Future set(String key, String value) async { - await future; - return SimpleSecureStorage.write(key, value); - } + Future set(String key, String value) => + state.write(key: key, value: value); - Future clear() async { - await future; - return SimpleSecureStorage.clear(); - } + Future clear() => state.deleteAll(); - static final provider = AsyncNotifierProvider( - SecureStorageController.new, - ); + static final provider = + NotifierProvider( + SecureStorageController.new, + ); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index fd8ccf3..dffacff 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,10 +8,9 @@ #include #include +#include #include -#include #include -#include #include #include @@ -22,18 +21,15 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); - g_autoptr(FlPluginRegistrar) simple_secure_storage_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "SimpleSecureStorageLinuxPlugin"); - simple_secure_storage_linux_plugin_register_with_registrar(simple_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); - g_autoptr(FlPluginRegistrar) webcrypto_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "WebcryptoPlugin"); - webcrypto_plugin_register_with_registrar(webcrypto_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 8d79b66..1cac43c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,10 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_linux + flutter_secure_storage_linux screen_retriever_linux - simple_secure_storage_linux url_launcher_linux - webcrypto window_manager window_size ) diff --git a/pubspec.lock b/pubspec.lock index 1c5df6c..aeb2553 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -517,6 +517,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + url: "https://pub.dev" + source: hosted + version: "10.0.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + url: "https://pub.dev" + source: hosted + version: "4.1.0" flutter_svg: dependency: "direct main" description: @@ -808,14 +856,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.11.3" - just_throttle_it: - dependency: transitive - description: - name: just_throttle_it - sha256: af2d0c1e5c7f4e0bef79a55edf3d74c180908253f89203467bc432730f5fac5b - url: "https://pub.dev" - source: hosted - version: "3.0.1" leak_tracker: dependency: transitive description: @@ -1176,14 +1216,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.8.5+2" - sembast_web: - dependency: transitive - description: - name: sembast_web - sha256: "0362c7c241ad6546d3e27b4cfffaae505e5a9661e238dbcdd176756cc960fe7a" - url: "https://pub.dev" - source: hosted - version: "2.4.2" shared_preferences: dependency: "direct main" description: @@ -1272,62 +1304,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" - simple_secure_storage: - dependency: "direct main" - description: - name: simple_secure_storage - sha256: ca823a355bb7bb0e9b969876508e7d3a5dc0d1fb2dcb681c85b6e315f1e876e9 - url: "https://pub.dev" - source: hosted - version: "0.3.7" - simple_secure_storage_android: - dependency: transitive - description: - name: simple_secure_storage_android - sha256: "50fb27267755843af039da116d0e545f313ae329ef8838101880802259e0f741" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - simple_secure_storage_darwin: - dependency: transitive - description: - name: simple_secure_storage_darwin - sha256: "8bd2ffcc62b478957ce20046bb96618b91a11e74af5d9fe2b4b229117bad18a7" - url: "https://pub.dev" - source: hosted - version: "0.2.2" - simple_secure_storage_linux: - dependency: transitive - description: - name: simple_secure_storage_linux - sha256: a7b7dccfaf496c27f882c26634ac083f2f545c0a4ca0818534c6261205a83686 - url: "https://pub.dev" - source: hosted - version: "0.2.5" - simple_secure_storage_platform_interface: - dependency: transitive - description: - name: simple_secure_storage_platform_interface - sha256: "04fd4ce4c2b97c01a12eba46f51e3075a793d11f13340d06a64eb9b45a463ca5" - url: "https://pub.dev" - source: hosted - version: "0.2.3" - simple_secure_storage_web: - dependency: transitive - description: - name: simple_secure_storage_web - sha256: "63a3474a9931ab2587e01d22e7e95c0b7cc31338c0fafed5db9d1d798d1d3e0e" - url: "https://pub.dev" - source: hosted - version: "0.2.3" - simple_secure_storage_windows: - dependency: transitive - description: - name: simple_secure_storage_windows - sha256: cf31d2a97c26cf854aeb3c9774cd253f6600fb3fdfc6d807d480afae678cef10 - url: "https://pub.dev" - source: hosted - version: "0.3.2" sky_engine: dependency: transitive description: flutter @@ -1670,14 +1646,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - webcrypto: - dependency: transitive - description: - name: webcrypto - sha256: "6b43001c4110856ff7fa5e5e65e7b2d44bec1d8b54a4d84d5fa2c7622267c5c1" - url: "https://pub.dev" - source: hosted - version: "0.6.0" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 29a9055..590c1b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,12 +65,12 @@ dependencies: flutter_vodozemac: ^0.4.1 flutter_widget_from_html_core: ^0.17.0 flutter_svg: ^2.2.2 - simple_secure_storage: ^0.3.6 json_annotation: ^4.9.0 vodozemac: ^0.4.0 shared_preferences: ^2.5.3 mention_tag_text_field: ^0.0.9 fluttertagger: ^2.3.1 + flutter_secure_storage: ^10.0.0 dev_dependencies: build_runner: ^2.4.11 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 9d7af86..b12edca 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,9 @@ #include #include +#include #include -#include #include -#include #include #include @@ -20,14 +19,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); - SimpleSecureStorageWindowsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SimpleSecureStorageWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); - WebcryptoPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("WebcryptoPlugin")); WindowManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowManagerPlugin")); WindowSizePluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index dcf3309..3c6fdca 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,10 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_system_colors file_selector_windows + flutter_secure_storage_windows screen_retriever_windows - simple_secure_storage_windows url_launcher_windows - webcrypto window_manager window_size ) From 78a3bd63dd0e350550dc8627c8db4e21a23e7578 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 18:53:08 -0500 Subject: [PATCH 36/50] rename windows action --- .github/workflows/{main.yml => windows.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{main.yml => windows.yml} (97%) diff --git a/.github/workflows/main.yml b/.github/workflows/windows.yml similarity index 97% rename from .github/workflows/main.yml rename to .github/workflows/windows.yml index 75815b8..9cf60fb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/windows.yml @@ -1,4 +1,4 @@ -name: "Build Windows Installer" +name: "Build Windows Version" on: workflow_dispatch: From 0d80c93bc74febc171a10d6008aa820537e7a000 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 19:01:57 -0500 Subject: [PATCH 37/50] add windows support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e02259..e7a93c2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Platform Support - [x] Linux - - [x] Windows: Untested, waiting for testers + - [x] Windows - [ ] MacOS - [ ] Android - [ ] iOS From baf26d0ec9729d27fca552fbe8ee3d90b84802ae Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 20:11:10 -0500 Subject: [PATCH 38/50] Add maximize support and some other stuff --- lib/main.dart | 10 ++++- lib/widgets/appbar.dart | 52 ++++++++++++++++------ lib/widgets/chat_page/chat_box.dart | 1 + lib/widgets/chat_page/mention_overlay.dart | 2 + 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index bf65f75..9e829a7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +import "dart:io"; + import "package:flutter/foundation.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; @@ -58,11 +60,15 @@ void main() async { WindowOptions(titleBarStyle: TitleBarStyle.hidden), ); + if (Platform.isLinux) { + setWindowMinSize(const Size.square(500)); + } else { + await windowManager.setMinimumSize(Size.square(500)); + } + FlutterError.onError = (FlutterErrorDetails details) => showError(details.exception.toString(), details.stack); - setWindowMinSize(const Size.square(500)); - runApp( ProviderScope( observers: [ diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index 3ecaa1d..3d9315e 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -1,5 +1,7 @@ import "dart:io"; +import "dart:ui"; import "package:flutter/material.dart"; +import "package:window_manager/window_manager.dart"; class Appbar extends StatelessWidget implements PreferredSizeWidget { final Widget? leading; @@ -7,6 +9,7 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget { final Color? backgroundColor; final double? scrolledUnderElevation; final List actions; + const Appbar({ super.key, this.title, @@ -17,19 +20,42 @@ class Appbar extends StatelessWidget implements PreferredSizeWidget { }); @override - Size get preferredSize => AppBar().preferredSize; + Size get preferredSize => const Size.fromHeight(kToolbarHeight); @override - AppBar build(BuildContext context) => AppBar( - leading: leading, - backgroundColor: backgroundColor, - scrolledUnderElevation: scrolledUnderElevation, - actionsPadding: EdgeInsets.symmetric(horizontal: 8), - title: title, - actions: [ - ...actions, - if (!(Platform.isAndroid || Platform.isIOS)) - IconButton(onPressed: () => exit(0), icon: Icon(Icons.close)), - ], - ); + Widget build(BuildContext context) { + Future maximize() async { + final isMaximized = await windowManager.isMaximized(); + + if (isMaximized) { + return windowManager.unmaximize(); + } + + return windowManager.maximize(); + } + + return GestureDetector( + behavior: HitTestBehavior.translucent, + onDoubleTap: maximize, + onPanStart: (_) => windowManager.startDragging(), + child: AppBar( + leading: leading, + backgroundColor: backgroundColor, + scrolledUnderElevation: scrolledUnderElevation, + actionsPadding: const EdgeInsets.symmetric(horizontal: 8), + title: title, + actions: [ + ...actions, + if (!(Platform.isAndroid || Platform.isIOS)) ...[ + if (!Platform.isLinux) + IconButton( + onPressed: maximize, + icon: const Icon(Icons.fullscreen), + ), + IconButton(onPressed: () => exit(0), icon: const Icon(Icons.close)), + ], + ], + ), + ); + } } diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index 23331af..3497ce9 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -45,6 +45,7 @@ class ChatBox extends HookConsumerWidget { } void send() { + if (controller.value.text.isEmpty) return; ref .watch(RoomChatController.provider(room).notifier) .send( diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart index 6558a9d..8477f22 100644 --- a/lib/widgets/chat_page/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -22,6 +22,8 @@ class MentionOverlay extends ConsumerWidget { super.key, }); + // TODO: Don't embed mentions + @override Widget build(BuildContext context, WidgetRef ref) => Padding( padding: EdgeInsets.all(8), From 1583d50805f2689308e5a6b8bcc95f9c5a3b5328 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 20:15:25 -0500 Subject: [PATCH 39/50] fix regex --- pubspec.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index aeb2553..c24a860 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -365,10 +365,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" file: dependency: transitive description: @@ -443,7 +443,7 @@ packages: description: path: "packages/flutter_chat_ui" ref: HEAD - resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" + resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git version: "2.11.1" @@ -468,7 +468,7 @@ packages: description: path: "packages/flutter_link_previewer" ref: HEAD - resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" + resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git version: "4.2.0" @@ -636,7 +636,7 @@ packages: description: path: "packages/flyer_chat_text_message" ref: HEAD - resolved-ref: "82a10e471e7b38ee709b7555b47916903c8d64a4" + resolved-ref: "03be67c8c81c8f637672ee03dd8f082d2c223627" url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git version: "2.6.0" @@ -740,10 +740,10 @@ packages: dependency: transitive description: name: idb_shim - sha256: "071f3b05032fa62e60ca15db9939f8afbaf403b37e67747ac88f858c3e999228" + sha256: b26b2ad126be411d0072d1dfc4d97ebe02121a863e4eadc635b511b9bc138489 url: "https://pub.dev" source: hosted - version: "2.6.7+1" + version: "2.7.1+2" image: dependency: transitive description: @@ -1212,10 +1212,10 @@ packages: dependency: transitive description: name: sembast - sha256: c8063c3146c3c8d5f5b04230de7682c768440a575fbda2634f14d22f263197c3 + sha256: "139cf71496105de32e7a08a4e3a1ead0f81c4a616ec9703ed07e8f0d10cdd505" url: "https://pub.dev" source: hosted - version: "3.8.5+2" + version: "3.8.6" shared_preferences: dependency: "direct main" description: @@ -1618,10 +1618,10 @@ packages: dependency: transitive description: name: watcher - sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" web: dependency: transitive description: @@ -1720,5 +1720,5 @@ packages: source: hosted version: "2.2.3" sdks: - dart: ">=3.9.2 <4.0.0" + dart: ">=3.10.0 <4.0.0" flutter: ">=3.35.0" From 125c78d9d23a74d811441a039759b925c34939fc Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 20:55:21 -0500 Subject: [PATCH 40/50] Fix login issue --- lib/controllers/client_controller.dart | 4 +++- lib/widgets/chat_page/mention_overlay.dart | 2 +- lib/widgets/chat_page/room_appbar.dart | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index ad25a30..263e488 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -15,7 +15,9 @@ class ClientController extends AsyncNotifier { bool updateShouldNotify( AsyncValue previous, AsyncValue next, - ) => previous.hasValue != next.hasValue; + ) => + previous.hasValue != next.hasValue || + previous.value?.accessToken != next.value?.accessToken; static const sessionBackupKey = "sessionBackup"; @override diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart index 8477f22..af9e585 100644 --- a/lib/widgets/chat_page/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -108,7 +108,7 @@ class MentionOverlay extends ConsumerWidget { title: Text(room.title), subtitle: room.roomData.topic.isEmpty ? null - : Text(room.roomData.topic), + : Text(room.roomData.topic, maxLines: 1), onTap: () => addTag( id: "[#${room.roomData.getLocalizedDisplayname()}](https://matrix.to/#/${room.roomData.id})", name: diff --git a/lib/widgets/chat_page/room_appbar.dart b/lib/widgets/chat_page/room_appbar.dart index 17696dd..b36a3ad 100644 --- a/lib/widgets/chat_page/room_appbar.dart +++ b/lib/widgets/chat_page/room_appbar.dart @@ -36,7 +36,7 @@ class RoomAppbar extends StatelessWidget implements PreferredSizeWidget { title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(room.title, overflow: TextOverflow.ellipsis), + Text(room.title, overflow: TextOverflow.ellipsis, maxLines: 1), if (room.roomData.topic.isNotEmpty) Text( room.roomData.topic, From 3012bebe934eab8e9640e131199a468573ebec88 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 21:00:38 -0500 Subject: [PATCH 41/50] trim text before checking if empty --- lib/widgets/chat_page/chat_box.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/chat_page/chat_box.dart b/lib/widgets/chat_page/chat_box.dart index 3497ce9..8fd5acb 100644 --- a/lib/widgets/chat_page/chat_box.dart +++ b/lib/widgets/chat_page/chat_box.dart @@ -45,7 +45,7 @@ class ChatBox extends HookConsumerWidget { } void send() { - if (controller.value.text.isEmpty) return; + if (controller.value.text.trim().isEmpty) return; ref .watch(RoomChatController.provider(room).notifier) .send( From 43b11ff475a80ff093e22eccfd2081ce2db8d62c Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 21:07:09 -0500 Subject: [PATCH 42/50] remove some un-needed code --- .vscode/settings.json | 8 +++++++- lib/widgets/appbar.dart | 1 - lib/widgets/chat_page/mention_overlay.dart | 2 -- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 30f4254..25ea52b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "cSpell.words": ["Appbar", "Displayname", "prefs"] + "cSpell.words": [ + "Appbar", + "Displayname", + "Homeserver", + "prefs", + "vodozemac" + ] } diff --git a/lib/widgets/appbar.dart b/lib/widgets/appbar.dart index 3d9315e..fa2088d 100644 --- a/lib/widgets/appbar.dart +++ b/lib/widgets/appbar.dart @@ -1,5 +1,4 @@ import "dart:io"; -import "dart:ui"; import "package:flutter/material.dart"; import "package:window_manager/window_manager.dart"; diff --git a/lib/widgets/chat_page/mention_overlay.dart b/lib/widgets/chat_page/mention_overlay.dart index af9e585..f6c7fe3 100644 --- a/lib/widgets/chat_page/mention_overlay.dart +++ b/lib/widgets/chat_page/mention_overlay.dart @@ -22,8 +22,6 @@ class MentionOverlay extends ConsumerWidget { super.key, }); - // TODO: Don't embed mentions - @override Widget build(BuildContext context, WidgetRef ref) => Padding( padding: EdgeInsets.all(8), From e64bea33c9cf8c26ee4d50f144cb53869f23f15b Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Thu, 8 Jan 2026 21:35:09 -0500 Subject: [PATCH 43/50] add icon --- pubspec.yaml | 4 +++- windows/runner/resources/app_icon.ico | Bin 33772 -> 4251 bytes 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 590c1b0..2e3ddfd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -87,4 +87,6 @@ flutter_launcher_icons: image_path: assets/icon.png adaptive_icon_background: "#000000" adaptive_icon_foreground: assets/foreground.png - remove_alpha_ios: true \ No newline at end of file + remove_alpha_ios: true + windows: + generate: true \ No newline at end of file diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20caf6370ebb9253ad831cc31de4a9c965f6..e3c83c9b76ac18aa5f9fc7da1df80f5112e06833 100644 GIT binary patch literal 4251 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 nI|D-_GXq0IBLl;MdIp9E_6!UkK Date: Thu, 8 Jan 2026 22:11:58 -0500 Subject: [PATCH 44/50] use iss from windows --- .github/workflows/windows.yml | 2 +- installer.iss => windows/installer.iss | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename installer.iss => windows/installer.iss (100%) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9cf60fb..9814fa0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -37,7 +37,7 @@ jobs: run: choco install innosetup -y - name: "Build Inno Setup installer" - run: iscc installer.iss + run: iscc windows/installer.iss - name: "Upload installer artifact" uses: "actions/upload-artifact@v4" diff --git a/installer.iss b/windows/installer.iss similarity index 100% rename from installer.iss rename to windows/installer.iss From d0cb57e41f1b889ccb24b29b6507ff43d9e881a7 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 9 Jan 2026 09:29:46 -0500 Subject: [PATCH 45/50] grab installer from windows --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9814fa0..c8099d1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -43,4 +43,4 @@ jobs: uses: "actions/upload-artifact@v4" with: name: "windows-installer" - path: "dist/Nexus-Setup.exe" + path: "windows/dist/Nexus-Setup.exe" From 8ba9f170cc8ecd09f5eba1a7b15b5d31c883653b Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 9 Jan 2026 09:42:29 -0500 Subject: [PATCH 46/50] fix build --- windows/installer.iss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/installer.iss b/windows/installer.iss index 96c48e3..c5004c3 100644 --- a/windows/installer.iss +++ b/windows/installer.iss @@ -10,7 +10,7 @@ SolidCompression=yes ArchitecturesInstallIn64BitMode=x64 [Files] -Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion +Source: "..\build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion [Icons] Name: "{group}\Nexus"; Filename: "{app}\nexus.exe" From 396a0f4c4fb0c4cac495d9a20c64b27571491131 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sat, 10 Jan 2026 10:17:57 -0500 Subject: [PATCH 47/50] i cant get old encryption keys to load :/ --- lib/controllers/client_controller.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/controllers/client_controller.dart b/lib/controllers/client_controller.dart index 263e488..9f69e8f 100644 --- a/lib/controllers/client_controller.dart +++ b/lib/controllers/client_controller.dart @@ -56,12 +56,6 @@ class ClientController extends AsyncNotifier { ); } - if (client.userID != null) { - // client.encryption?.keyVerificationManager.addRequest( - // KeyVerification(encryption: client.encryption!, userId: client.userID!), - // ); - } - return client; } From a1c7349b3b6299e489e49a765056a29aeafd70ce Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sat, 10 Jan 2026 10:21:28 -0500 Subject: [PATCH 48/50] use a widget for edited --- lib/widgets/chat_page/html/html.dart | 2 -- lib/widgets/chat_page/room_chat.dart | 24 +++++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/widgets/chat_page/html/html.dart b/lib/widgets/chat_page/html/html.dart index ce5318a..04a5a1b 100644 --- a/lib/widgets/chat_page/html/html.dart +++ b/lib/widgets/chat_page/html/html.dart @@ -142,8 +142,6 @@ class Html extends ConsumerWidget { "data-mx-bg-color" => MapEntry("background-color", value), - "edited" => MapEntry("display", "block"), - _ => null, }, ) diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index d4827be..28df7a4 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -244,8 +244,12 @@ class RoomChat extends HookConsumerWidget { required bool isSentByMe, MessageGroupStatus? groupStatus, }) => FlyerChatTextMessage( - customWidget: Html( - (message.metadata?["formatted"] + customWidget: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Html( + (message.metadata?["formatted"] as String) .replaceAllMapped( RegExp( @@ -264,11 +268,17 @@ class RoomChat extends HookConsumerWidget { return "$url"; }, ) - .replaceAll("\n", "
") + - ((message.editedAt != null) - ? "(edited)" - : ""), - client: room.roomData.client, + .replaceAll("\n", "
"), + client: room.roomData.client, + ), + if (message.editedAt != null) + Text( + "(edited)", + style: theme + .textTheme + .labelSmall, + ), + ], ), topWidget: TopWidget( message, From 5ba368011126bcd1044a7cef6bb444c6058bb16b Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sat, 10 Jan 2026 11:55:41 -0500 Subject: [PATCH 49/50] Add blurhash support --- README.md | 2 ++ lib/helpers/extensions/event_to_message.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index e7a93c2..864e399 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Jump to original message - [x] Edits - [x] Attachments + - [x] Blurhashing - [ ] Downloading attachments - [ ] Opening attachments in their own view - [x] Mentions @@ -87,6 +88,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [ ] Spam filtering - [ ] Settings - [ ] Light/Dark mode + - [ ] Show media by default - [ ] Dynamic Theming - [ ] Devices - [ ] Viewing devices diff --git a/lib/helpers/extensions/event_to_message.dart b/lib/helpers/extensions/event_to_message.dart index 28d9f4f..2481388 100644 --- a/lib/helpers/extensions/event_to_message.dart +++ b/lib/helpers/extensions/event_to_message.dart @@ -80,6 +80,7 @@ extension EventToMessage on Event { source: (await getAttachmentUri()).toString(), replyToMessageId: replyId, deliveredAt: originServerTs, + blurhash: (event.content["info"] as Map?)?["xyz.amorgan.blurhash"], ), MessageTypes.Audio => Message.audio( metadata: metadata, From b9ac38e7df9a9cc52a7da8948e616831c3d933b4 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Sat, 10 Jan 2026 12:18:16 -0500 Subject: [PATCH 50/50] add polls to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 864e399..f67c963 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ A simple and user-friendly Matrix client made with Flutter and the Matrix Dart S - [x] Blurhashing - [ ] Downloading attachments - [ ] Opening attachments in their own view + - [ ] Polls - [x] Mentions - [x] Users - [x] Rooms