From 5dc8fe14bd1005d4ce83c78f8893ed40c7b7b1ec Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 28 Nov 2025 18:24:27 -0500 Subject: [PATCH] Refactors --- lib/controllers/current_room_controller.dart | 2 +- lib/controllers/message_controller.dart | 2 +- lib/controllers/room_chat_controller.dart | 4 +- lib/controllers/spaces_controller.dart | 2 +- lib/helpers/extensions/better_when.dart | 17 ++++++ lib/helpers/extensions/get_full_room.dart | 13 ++++ lib/helpers/extensions/get_headers.dart | 5 ++ .../to_message.dart} | 58 ------------------ lib/helpers/extensions/to_messages.dart | 15 +++++ lib/helpers/extensions/to_theme.dart | 14 +++++ lib/main.dart | 3 +- lib/widgets/chat_page/member_list.dart | 3 +- lib/widgets/chat_page/room_appbar.dart | 2 +- lib/widgets/chat_page/room_chat.dart | 60 ++++++++++++------- lib/widgets/chat_page/sidebar.dart | 3 +- lib/widgets/chat_page/top_widget.dart | 2 +- pubspec.lock | 11 ++-- pubspec.yaml | 5 +- 18 files changed, 126 insertions(+), 95 deletions(-) create mode 100644 lib/helpers/extensions/better_when.dart create mode 100644 lib/helpers/extensions/get_full_room.dart create mode 100644 lib/helpers/extensions/get_headers.dart rename lib/helpers/{extension_helper.dart => extensions/to_message.dart} (64%) create mode 100644 lib/helpers/extensions/to_messages.dart create mode 100644 lib/helpers/extensions/to_theme.dart diff --git a/lib/controllers/current_room_controller.dart b/lib/controllers/current_room_controller.dart index 8e09ef1..bcef708 100644 --- a/lib/controllers/current_room_controller.dart +++ b/lib/controllers/current_room_controller.dart @@ -1,6 +1,6 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/spaces_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/get_full_room.dart"; import "package:nexus/models/full_room.dart"; class CurrentRoomController extends AsyncNotifier { diff --git a/lib/controllers/message_controller.dart b/lib/controllers/message_controller.dart index 4901931..fc9218f 100644 --- a/lib/controllers/message_controller.dart +++ b/lib/controllers/message_controller.dart @@ -1,7 +1,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/current_room_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/to_message.dart"; class MessageController extends AsyncNotifier { final String id; diff --git a/lib/controllers/room_chat_controller.dart b/lib/controllers/room_chat_controller.dart index 9fd1445..fc9b218 100644 --- a/lib/controllers/room_chat_controller.dart +++ b/lib/controllers/room_chat_controller.dart @@ -4,7 +4,8 @@ import "package:flutter_chat_core/flutter_chat_core.dart" as chat; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:matrix/matrix.dart"; import "package:nexus/controllers/events_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/to_message.dart"; +import "package:nexus/helpers/extensions/to_messages.dart"; class RoomChatController extends AsyncNotifier { final Room room; @@ -32,6 +33,7 @@ class RoomChatController extends AsyncNotifier { } }).cancel, ); + return InMemoryChatController( messages: await response.chunk.toMessages(room), ); diff --git a/lib/controllers/spaces_controller.dart b/lib/controllers/spaces_controller.dart index 228f07c..733770c 100644 --- a/lib/controllers/spaces_controller.dart +++ b/lib/controllers/spaces_controller.dart @@ -3,7 +3,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/material.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/get_full_room.dart"; import "package:nexus/models/space.dart"; class SpacesController extends AsyncNotifier> { diff --git a/lib/helpers/extensions/better_when.dart b/lib/helpers/extensions/better_when.dart new file mode 100644 index 0000000..beaae85 --- /dev/null +++ b/lib/helpers/extensions/better_when.dart @@ -0,0 +1,17 @@ +import "package:flutter/widgets.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/widgets/error_dialog.dart"; +import "package:nexus/widgets/loading.dart"; + +extension BetterWhen on AsyncValue { + Widget betterWhen({ + required Widget Function(T value) data, + Widget Function() loading = Loading.new, + bool skipLoadingOnRefresh = false, + }) => when( + data: data, + error: (error, stackTrace) => ErrorDialog(error, stackTrace), + loading: loading, + skipLoadingOnRefresh: skipLoadingOnRefresh, + ); +} diff --git a/lib/helpers/extensions/get_full_room.dart b/lib/helpers/extensions/get_full_room.dart new file mode 100644 index 0000000..bbd0bc5 --- /dev/null +++ b/lib/helpers/extensions/get_full_room.dart @@ -0,0 +1,13 @@ +import "package:matrix/matrix.dart"; +import "package:nexus/models/full_room.dart"; + +extension GetFullRoom on Room { + Future get fullRoom async { + await loadHeroUsers(); + return FullRoom( + roomData: this, + title: getLocalizedDisplayname(), + avatar: await avatar?.getThumbnailUri(client, width: 24, height: 24), + ); + } +} diff --git a/lib/helpers/extensions/get_headers.dart b/lib/helpers/extensions/get_headers.dart new file mode 100644 index 0000000..b8b1fde --- /dev/null +++ b/lib/helpers/extensions/get_headers.dart @@ -0,0 +1,5 @@ +import "package:matrix/matrix.dart"; + +extension GetHeaders on Client { + Map get headers => {"authorization": "Bearer $accessToken"}; +} diff --git a/lib/helpers/extension_helper.dart b/lib/helpers/extensions/to_message.dart similarity index 64% rename from lib/helpers/extension_helper.dart rename to lib/helpers/extensions/to_message.dart index 26199ef..2689cea 100644 --- a/lib/helpers/extension_helper.dart +++ b/lib/helpers/extensions/to_message.dart @@ -1,38 +1,5 @@ -import "package:flutter/material.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:matrix/matrix.dart"; -import "package:nexus/models/full_room.dart"; -import "package:nexus/widgets/error_dialog.dart"; -import "package:nexus/widgets/loading.dart"; - -extension BetterWhen on AsyncValue { - Widget betterWhen({ - required Widget Function(T value) data, - Widget Function() loading = Loading.new, - bool skipLoadingOnRefresh = false, - }) => when( - data: data, - error: (error, stackTrace) => ErrorDialog(error, stackTrace), - loading: loading, - skipLoadingOnRefresh: skipLoadingOnRefresh, - ); -} - -extension GetFullRoom on Room { - Future get fullRoom async { - await loadHeroUsers(); - return FullRoom( - roomData: this, - title: getLocalizedDisplayname(), - avatar: await avatar?.getThumbnailUri(client, width: 24, height: 24), - ); - } -} - -extension GetHeaders on Client { - Map get headers => {"authorization": "Bearer $accessToken"}; -} extension ToMessage on Event { Future toMessage({bool mustBeText = false}) async { @@ -129,28 +96,3 @@ extension ToMessage on Event { }; } } - -extension ToTheme on ColorScheme { - ThemeData get theme => ThemeData.from(colorScheme: this).copyWith( - cardTheme: CardThemeData(color: primaryContainer), - appBarTheme: AppBarTheme( - titleSpacing: 0, - backgroundColor: surfaceContainerLow, - ), - inputDecorationTheme: const InputDecorationTheme( - border: OutlineInputBorder(), - ), - ); -} - -extension ToMessages on List { - Future> toMessages(Room room) async { - final messages = await Future.wait( - map((event) => Event.fromMatrixEvent(event, room).toMessage()), - ); - - return { - for (var msg in messages.nonNulls.toList().reversed.toList()) msg.id: msg, - }.values.toList(); - } -} diff --git a/lib/helpers/extensions/to_messages.dart b/lib/helpers/extensions/to_messages.dart new file mode 100644 index 0000000..81e1618 --- /dev/null +++ b/lib/helpers/extensions/to_messages.dart @@ -0,0 +1,15 @@ +import "package:flutter_chat_core/flutter_chat_core.dart"; +import "package:matrix/matrix.dart"; +import "package:nexus/helpers/extensions/to_message.dart"; + +extension ToMessages on List { + Future> toMessages(Room room) async { + final messages = await Future.wait( + map((event) => Event.fromMatrixEvent(event, room).toMessage()), + ); + + return { + for (var msg in messages.nonNulls.toList().reversed.toList()) msg.id: msg, + }.values.toList(); + } +} diff --git a/lib/helpers/extensions/to_theme.dart b/lib/helpers/extensions/to_theme.dart new file mode 100644 index 0000000..61edd69 --- /dev/null +++ b/lib/helpers/extensions/to_theme.dart @@ -0,0 +1,14 @@ +import "package:flutter/material.dart"; + +extension ToTheme on ColorScheme { + ThemeData get theme => ThemeData.from(colorScheme: this).copyWith( + cardTheme: CardThemeData(color: primaryContainer), + appBarTheme: AppBarTheme( + titleSpacing: 0, + backgroundColor: surfaceContainerLow, + ), + inputDecorationTheme: const InputDecorationTheme( + border: OutlineInputBorder(), + ), + ); +} diff --git a/lib/main.dart b/lib/main.dart index d32c259..c6daaf1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/helpers/extensions/to_theme.dart"; import "package:nexus/pages/chat_page.dart"; import "package:nexus/pages/login_page.dart"; import "package:window_manager/window_manager.dart"; diff --git a/lib/widgets/chat_page/member_list.dart b/lib/widgets/chat_page/member_list.dart index 2de1354..3e6e82e 100644 --- a/lib/widgets/chat_page/member_list.dart +++ b/lib/widgets/chat_page/member_list.dart @@ -3,7 +3,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:matrix/matrix.dart"; import "package:nexus/controllers/avatar_controller.dart"; import "package:nexus/controllers/members_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; class MemberList extends ConsumerWidget { diff --git a/lib/widgets/chat_page/room_appbar.dart b/lib/widgets/chat_page/room_appbar.dart index 83396e9..dbeb825 100644 --- a/lib/widgets/chat_page/room_appbar.dart +++ b/lib/widgets/chat_page/room_appbar.dart @@ -1,5 +1,5 @@ import "package:flutter/material.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/models/full_room.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; diff --git a/lib/widgets/chat_page/room_chat.dart b/lib/widgets/chat_page/room_chat.dart index b7e57f3..760e6d3 100644 --- a/lib/widgets/chat_page/room_chat.dart +++ b/lib/widgets/chat_page/room_chat.dart @@ -12,7 +12,8 @@ import "package:flyer_chat_text_message/flyer_chat_text_message.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/current_room_controller.dart"; import "package:nexus/controllers/room_chat_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/widgets/chat_page/chat_box.dart"; import "package:nexus/widgets/chat_page/code_block.dart"; @@ -36,20 +37,34 @@ class RoomChat extends HookConsumerWidget { required BuildContext context, required Offset globalPosition, required VoidCallback onTap, - }) => showMenu( - context: context, - position: RelativeRect.fromRect( - Rect.fromPoints(globalPosition, globalPosition), - Offset.zero & (context.findRenderObject() as RenderBox).size, - ), - color: Theme.of(context).colorScheme.surfaceContainerHighest, - items: [ - PopupMenuItem( - onTap: onTap, - child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")), + }) { + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + + showMenu( + context: context, + position: RelativeRect.fromLTRB( + globalPosition.dx, + globalPosition.dy, + overlay.size.width - globalPosition.dx, + overlay.size.height - globalPosition.dy, ), - ], - ); + color: Theme.of(context).colorScheme.surfaceContainerHighest, + items: [ + PopupMenuItem( + onTap: onTap, + child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")), + ), + PopupMenuItem( + onTap: onTap, + child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")), + ), + PopupMenuItem( + onTap: onTap, + child: ListTile(leading: Icon(Icons.delete), title: Text("Delete")), + ), + ], + ); + } @override Widget build(BuildContext context, WidgetRef ref) { @@ -143,14 +158,15 @@ class RoomChat extends HookConsumerWidget { MessageGroupStatus? groupStatus, }) => FlyerChatTextMessage( customWidget: HtmlWidget( - message.metadata?["formatted"].replaceAllMapped( - RegExp( - r'(?)(https?:\/\/[^\s<]+)', - caseSensitive: false, - ), - (m) => - "${m.group(0)!}", - ) + + message.metadata?["formatted"] + .replaceAllMapped( + RegExp( + regexLink, + caseSensitive: false, + ), + (m) => + "${m.group(0)!}", + ) + ((message.editedAt != null) ? "(edited)" : ""), diff --git a/lib/widgets/chat_page/sidebar.dart b/lib/widgets/chat_page/sidebar.dart index 2b82020..d8d579e 100644 --- a/lib/widgets/chat_page/sidebar.dart +++ b/lib/widgets/chat_page/sidebar.dart @@ -5,7 +5,8 @@ import "package:nexus/controllers/current_room_controller.dart"; import "package:nexus/controllers/selected_room_controller.dart"; import "package:nexus/controllers/selected_space_controller.dart"; import "package:nexus/controllers/spaces_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/helpers/extensions/get_headers.dart"; import "package:nexus/pages/settings_page.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; diff --git a/lib/widgets/chat_page/top_widget.dart b/lib/widgets/chat_page/top_widget.dart index 8dd92e6..dda5a17 100644 --- a/lib/widgets/chat_page/top_widget.dart +++ b/lib/widgets/chat_page/top_widget.dart @@ -4,7 +4,7 @@ import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_ui/flutter_chat_ui.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/message_controller.dart"; -import "package:nexus/helpers/extension_helper.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; class TopWidget extends ConsumerWidget { final Message message; diff --git a/pubspec.lock b/pubspec.lock index ac2c7b3..b88213e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -459,7 +459,7 @@ packages: description: path: "packages/flutter_chat_ui" ref: HEAD - resolved-ref: f6718923519db812762ff27eb402f70076d8676c + resolved-ref: bedc5d90130ad8a15b13f34f67b5b371e82cde7d url: "https://github.com/Henry-Hiles/flutter_chat_ui" source: git version: "2.9.1" @@ -482,10 +482,11 @@ packages: flutter_link_previewer: dependency: "direct main" description: - name: flutter_link_previewer - sha256: "62520ee224515f826dd40e4ccbb95e6d65ac060fbcf94d4620ce840ee1a15b83" - url: "https://pub.dev" - source: hosted + path: "packages/flutter_link_previewer" + ref: HEAD + resolved-ref: bedc5d90130ad8a15b13f34f67b5b371e82cde7d + url: "https://github.com/Henry-Hiles/flutter_chat_ui" + source: git version: "4.1.2" flutter_lints: dependency: "direct dev" diff --git a/pubspec.yaml b/pubspec.yaml index 33601fd..97090bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,7 +50,10 @@ dependencies: git: url: https://github.com/Henry-Hiles/flutter_chat_ui path: packages/flutter_chat_ui - flutter_link_previewer: ^4.1.2 + flutter_link_previewer: + git: + url: https://github.com/Henry-Hiles/flutter_chat_ui + path: packages/flutter_link_previewer matrix: ^3.0.2 sqflite_common_ffi: ^2.3.6 color_hash: ^1.0.1