diff --git a/lib/controllers/jump_to_event.dart b/lib/controllers/jump_to_event.dart new file mode 100644 index 0000000..389f077 --- /dev/null +++ b/lib/controllers/jump_to_event.dart @@ -0,0 +1,15 @@ +import "package:flutter_riverpod/flutter_riverpod.dart"; + +class JumpToEventController extends Notifier { + @override + String? build() => null; + + void set(String? eventId) => state = eventId; + + @override + bool updateShouldNotify(_, _) => true; + + static final provider = NotifierProvider( + JumpToEventController.new, + ); +} diff --git a/lib/controllers/notification.dart b/lib/controllers/notification.dart index 681c625..5ee34499 100644 --- a/lib/controllers/notification.dart +++ b/lib/controllers/notification.dart @@ -7,7 +7,7 @@ import "package:nexus/controllers/portal.dart"; import "package:nexus/main.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/pages/notifications.dart"; +import "package:nexus/widgets/pages/notifications.dart"; import "package:xdg_desktop_portal/xdg_desktop_portal.dart"; class NotificationController diff --git a/lib/helpers/hooks/chat_scroll.dart b/lib/helpers/hooks/chat_scroll.dart index bea65b9..3b1531eb 100644 --- a/lib/helpers/hooks/chat_scroll.dart +++ b/lib/helpers/hooks/chat_scroll.dart @@ -68,6 +68,14 @@ final class ChatScroll({ if (found || controllerData is AsyncError) { if (found) { anchorId.value = target; + + WidgetsBinding.instance.addPostFrameCallback((_) { + final context = keyFor(target).currentContext; + if (context != null && context.mounted) { + anchorMountedCompleter.value?.complete(context); + anchorMountedCompleter.value = null; + } + }); } else { anchorMountedCompleter.value?.completeError( StateError("Failed to load context for $target"), @@ -80,21 +88,6 @@ final class ChatScroll({ return null; }, [controllerData, pendingAnchorTarget.value]); - useEffect(() { - final completer = anchorMountedCompleter.value; - if (completer == null) return null; - - WidgetsBinding.instance.addPostFrameCallback((_) { - final context = keyFor(anchorId.value!).currentContext; - if (context != null && context.mounted) { - anchorMountedCompleter.value?.complete(context); - anchorMountedCompleter.value = null; - } - }); - - return null; - }, [anchorId.value]); - final ({IList history, IList live}) split = useMemoized(() { final items = controllerData.value?.timeline; final anchor = anchorId.value; diff --git a/lib/main.dart b/lib/main.dart index bbc8a4e..5586cbf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,10 +14,10 @@ import "package:nexus/controllers/settings.dart"; import "package:nexus/controllers/unified_push.dart"; import "package:nexus/helpers/extensions/scheme_to_theme.dart"; import "package:nexus/helpers/font_licenses.dart"; -import "package:nexus/pages/chat.dart"; -import "package:nexus/pages/select_server.dart"; -import "package:nexus/pages/settings.dart"; -import "package:nexus/pages/verify.dart"; +import "package:nexus/widgets/pages/chat.dart"; +import "package:nexus/widgets/pages/select_server.dart"; +import "package:nexus/widgets/pages/settings.dart"; +import "package:nexus/widgets/pages/verify.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/loading.dart"; diff --git a/lib/pages/chat.dart b/lib/widgets/pages/chat.dart similarity index 66% rename from lib/pages/chat.dart rename to lib/widgets/pages/chat.dart index 279f077..34efe5c 100644 --- a/lib/pages/chat.dart +++ b/lib/widgets/pages/chat.dart @@ -1,13 +1,14 @@ import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/init_complete.dart"; +import "package:nexus/controllers/jump_to_event.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/sidebar.dart"; import "package:nexus/widgets/room_chat/room_chat.dart"; import "package:nexus/widgets/loading.dart"; -class const ChatPage({super.key}) extends ConsumerWidget { +class const ChatPage({super.key}) extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( builder: (context, constraints) { @@ -26,11 +27,19 @@ class const ChatPage({super.key}) extends ConsumerWidget { children: [ if (isDesktop) Sidebar(isDesktop: isDesktop), Expanded( - child: RoomChat( - key: ValueKey(roomId), - roomId: roomId, - isDesktop: isDesktop, - showMembersByDefault: showMembersByDefault, + child: Consumer( + builder: (context, ref, _) { + final initialHighlight = ref.watch( + JumpToEventController.provider, + ); + return RoomChat( + key: ValueKey((roomId, initialHighlight)), + roomId: roomId, + isDesktop: isDesktop, + initialHighlightedEvent: initialHighlight, + showMembersByDefault: showMembersByDefault, + ); + }, ), ), ], diff --git a/lib/pages/notifications.dart b/lib/widgets/pages/notifications.dart similarity index 67% rename from lib/pages/notifications.dart rename to lib/widgets/pages/notifications.dart index 3e70104..c8cfe76 100644 --- a/lib/pages/notifications.dart +++ b/lib/widgets/pages/notifications.dart @@ -1,10 +1,14 @@ import "dart:async"; +import "package:collection/collection.dart"; import "package:m3e_buttons/m3e_buttons.dart"; import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:nexus/controllers/jump_to_event.dart"; +import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/notifications.dart"; +import "package:nexus/controllers/spaces.dart"; import "package:nexus/models/event.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/error_dialog.dart"; @@ -127,9 +131,54 @@ class const NotificationsPage({ child: HighlightWrapper( InkWell( onTap: () { - // TODO: Jump to event + final spaces = ref.read( + SpacesController.provider, + ); + final space = spaces.firstWhereOrNull( + (space) => + space.children.any( + (room) => + room.metadata?.id == + event.roomId, + ) || + space.subSpaces.any( + (subSpace) => + subSpace.children.any( + (room) => + room.metadata?.id == + event.roomId, + ), + ), + ); + if (space == null) return; + + ref + .read( + KeyController.provider( + KeyController.spaceKey, + ).notifier, + ) + .set(space.id); + ref + .read( + KeyController.provider( + KeyController.roomKey, + ).notifier, + ) + .set(event.roomId); + ref + .read( + JumpToEventController + .provider + .notifier, + ) + .set(event.eventId); + + Navigator.of(context).pop(); }, - child: EventRenderer(event), + child: IgnorePointer( + child: EventRenderer(event), + ), ), isHighlighted: isHighlighted, ), diff --git a/lib/pages/select_server.dart b/lib/widgets/pages/select_server.dart similarity index 99% rename from lib/pages/select_server.dart rename to lib/widgets/pages/select_server.dart index a903cf6..34f1a50 100644 --- a/lib/pages/select_server.dart +++ b/lib/widgets/pages/select_server.dart @@ -9,7 +9,7 @@ import "package:nexus/controllers/client_id.dart"; import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/main.dart"; import "package:nexus/models/homeserver.dart"; -import "package:nexus/pages/settings.dart"; +import "package:nexus/widgets/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/divider_text.dart"; diff --git a/lib/pages/settings.dart b/lib/widgets/pages/settings.dart similarity index 99% rename from lib/pages/settings.dart rename to lib/widgets/pages/settings.dart index ed9dc59..50eb7f0 100644 --- a/lib/pages/settings.dart +++ b/lib/widgets/pages/settings.dart @@ -8,7 +8,7 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/settings_sections.dart"; import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/show_about_dialog.dart"; -import "package:nexus/pages/settings_category.dart"; +import "package:nexus/widgets/pages/settings_category.dart"; import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/highlight_wrapper.dart"; import "package:super_sliver_list/super_sliver_list.dart"; diff --git a/lib/pages/settings_category.dart b/lib/widgets/pages/settings_category.dart similarity index 100% rename from lib/pages/settings_category.dart rename to lib/widgets/pages/settings_category.dart diff --git a/lib/pages/verify.dart b/lib/widgets/pages/verify.dart similarity index 98% rename from lib/pages/verify.dart rename to lib/widgets/pages/verify.dart index 42a8dfc..a4f95ec 100644 --- a/lib/pages/verify.dart +++ b/lib/widgets/pages/verify.dart @@ -2,7 +2,7 @@ import "package:material_ui/material_ui.dart"; import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:nexus/controllers/client.dart"; -import "package:nexus/pages/settings.dart"; +import "package:nexus/widgets/pages/settings.dart"; import "package:nexus/widgets/appbar.dart"; import "package:nexus/helpers/required_validator_helper.dart"; diff --git a/lib/widgets/room_chat/room_chat.dart b/lib/widgets/room_chat/room_chat.dart index f58bda0..709d22d 100644 --- a/lib/widgets/room_chat/room_chat.dart +++ b/lib/widgets/room_chat/room_chat.dart @@ -32,7 +32,7 @@ final class const RoomChat({ final relatedEvent = useState(null); final relationType = useState(RelationType.reply); final contextualEvent = useState(initialHighlightedEvent); - final highlightedEvent = useState(initialHighlightedEvent); + final highlightedEvent = useState(null); final composerSize = useState(64); @@ -106,6 +106,24 @@ final class const RoomChat({ }); } + useEffect(() { + if (initialHighlightedEvent == null) return null; + + void check() { + if (!context.mounted) return; + + if (scroll.scrollController.hasClients) { + jumpToId(initialHighlightedEvent!); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) => check()); + } + } + + check(); + + return null; + }, [initialHighlightedEvent]); + IList getEventOptions(Event event) => event.buildEventOptions( context: context, diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index b2d8295..6689ce2 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -6,8 +6,8 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/spaces.dart"; import "package:nexus/models/room.dart"; -import "package:nexus/pages/notifications.dart"; -import "package:nexus/pages/settings.dart"; +import "package:nexus/widgets/pages/notifications.dart"; +import "package:nexus/widgets/pages/settings.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/join_dialog.dart";