Compare commits

..
12 changed files with 118 additions and 34 deletions

View file

@ -0,0 +1,15 @@
import "package:flutter_riverpod/flutter_riverpod.dart";
class JumpToEventController extends Notifier<String?> {
@override
String? build() => null;
void set(String? eventId) => state = eventId;
@override
bool updateShouldNotify(_, _) => true;
static final provider = NotifierProvider<JumpToEventController, String?>(
JumpToEventController.new,
);
}

View file

@ -7,7 +7,7 @@ import "package:nexus/controllers/portal.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:flutter_local_notifications/flutter_local_notifications.dart"; import "package:flutter_local_notifications/flutter_local_notifications.dart";
import "package:flutter_riverpod/flutter_riverpod.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"; import "package:xdg_desktop_portal/xdg_desktop_portal.dart";
class NotificationController class NotificationController

View file

@ -68,6 +68,14 @@ final class ChatScroll({
if (found || controllerData is AsyncError) { if (found || controllerData is AsyncError) {
if (found) { if (found) {
anchorId.value = target; anchorId.value = target;
WidgetsBinding.instance.addPostFrameCallback((_) {
final context = keyFor(target).currentContext;
if (context != null && context.mounted) {
anchorMountedCompleter.value?.complete(context);
anchorMountedCompleter.value = null;
}
});
} else { } else {
anchorMountedCompleter.value?.completeError( anchorMountedCompleter.value?.completeError(
StateError("Failed to load context for $target"), StateError("Failed to load context for $target"),
@ -80,21 +88,6 @@ final class ChatScroll({
return null; return null;
}, [controllerData, pendingAnchorTarget.value]); }, [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<Event> history, IList<Event> live}) split = useMemoized(() { final ({IList<Event> history, IList<Event> live}) split = useMemoized(() {
final items = controllerData.value?.timeline; final items = controllerData.value?.timeline;
final anchor = anchorId.value; final anchor = anchorId.value;

View file

@ -14,10 +14,10 @@ import "package:nexus/controllers/settings.dart";
import "package:nexus/controllers/unified_push.dart"; import "package:nexus/controllers/unified_push.dart";
import "package:nexus/helpers/extensions/scheme_to_theme.dart"; import "package:nexus/helpers/extensions/scheme_to_theme.dart";
import "package:nexus/helpers/font_licenses.dart"; import "package:nexus/helpers/font_licenses.dart";
import "package:nexus/pages/chat.dart"; import "package:nexus/widgets/pages/chat.dart";
import "package:nexus/pages/select_server.dart"; import "package:nexus/widgets/pages/select_server.dart";
import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/pages/settings.dart";
import "package:nexus/pages/verify.dart"; import "package:nexus/widgets/pages/verify.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";

View file

@ -1,13 +1,14 @@
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/init_complete.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/controllers/key.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/sidebar.dart"; import "package:nexus/widgets/sidebar.dart";
import "package:nexus/widgets/room_chat/room_chat.dart"; import "package:nexus/widgets/room_chat/room_chat.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
class const ChatPage({super.key}) extends ConsumerWidget { class const ChatPage({super.key}) extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder( Widget build(BuildContext context, WidgetRef ref) => LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@ -26,11 +27,19 @@ class const ChatPage({super.key}) extends ConsumerWidget {
children: [ children: [
if (isDesktop) Sidebar(isDesktop: isDesktop), if (isDesktop) Sidebar(isDesktop: isDesktop),
Expanded( Expanded(
child: RoomChat( child: Consumer(
key: ValueKey(roomId), builder: (context, ref, _) {
final initialHighlight = ref.watch(
JumpToEventController.provider,
);
return RoomChat(
key: ValueKey((roomId, initialHighlight)),
roomId: roomId, roomId: roomId,
isDesktop: isDesktop, isDesktop: isDesktop,
initialHighlightedEvent: initialHighlight,
showMembersByDefault: showMembersByDefault, showMembersByDefault: showMembersByDefault,
);
},
), ),
), ),
], ],

View file

@ -1,10 +1,14 @@
import "dart:async"; import "dart:async";
import "package:collection/collection.dart";
import "package:m3e_buttons/m3e_buttons.dart"; import "package:m3e_buttons/m3e_buttons.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.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/notifications.dart";
import "package:nexus/controllers/spaces.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
@ -127,10 +131,55 @@ class const NotificationsPage({
child: HighlightWrapper( child: HighlightWrapper(
InkWell( InkWell(
onTap: () { 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: IgnorePointer(
child: EventRenderer(event), child: EventRenderer(event),
), ),
),
isHighlighted: isHighlighted, isHighlighted: isHighlighted,
), ),
); );

View file

@ -9,7 +9,7 @@ import "package:nexus/controllers/client_id.dart";
import "package:nexus/helpers/launch_helper.dart"; import "package:nexus/helpers/launch_helper.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/models/homeserver.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/appbar.dart";
import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/divider_text.dart";

View file

@ -8,7 +8,7 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart";
import "package:nexus/controllers/settings_sections.dart"; import "package:nexus/controllers/settings_sections.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_about_dialog.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/divider_text.dart";
import "package:nexus/widgets/highlight_wrapper.dart"; import "package:nexus/widgets/highlight_wrapper.dart";
import "package:super_sliver_list/super_sliver_list.dart"; import "package:super_sliver_list/super_sliver_list.dart";

View file

@ -2,7 +2,7 @@ import "package:material_ui/material_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/client.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/widgets/appbar.dart";
import "package:nexus/helpers/required_validator_helper.dart"; import "package:nexus/helpers/required_validator_helper.dart";

View file

@ -32,7 +32,7 @@ final class const RoomChat({
final relatedEvent = useState<Event?>(null); final relatedEvent = useState<Event?>(null);
final relationType = useState(RelationType.reply); final relationType = useState(RelationType.reply);
final contextualEvent = useState<String?>(initialHighlightedEvent); final contextualEvent = useState<String?>(initialHighlightedEvent);
final highlightedEvent = useState<String?>(initialHighlightedEvent); final highlightedEvent = useState<String?>(null);
final composerSize = useState<double>(64); final composerSize = useState<double>(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<PopupMenuEntry> getEventOptions(Event event) => IList<PopupMenuEntry> getEventOptions(Event event) =>
event.buildEventOptions( event.buildEventOptions(
context: context, context: context,

View file

@ -6,8 +6,8 @@ import "package:navigation_rail_m3e/navigation_rail_m3e.dart";
import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/key.dart";
import "package:nexus/controllers/spaces.dart"; import "package:nexus/controllers/spaces.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/pages/notifications.dart"; import "package:nexus/widgets/pages/notifications.dart";
import "package:nexus/pages/settings.dart"; import "package:nexus/widgets/pages/settings.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/divider_widget.dart";
import "package:nexus/widgets/join_dialog.dart"; import "package:nexus/widgets/join_dialog.dart";