diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b471fb4..8eb6d3f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -32,6 +32,13 @@ + + + + + + + CFBundleURLSchemes nexus.federated.nexus + matrix diff --git a/lib/controllers/link_request.dart b/lib/controllers/link_request.dart new file mode 100644 index 0000000..0d7e7c2 --- /dev/null +++ b/lib/controllers/link_request.dart @@ -0,0 +1,21 @@ +import "package:app_links/app_links.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/helpers/extensions/get_link_info.dart"; + +class LinkRequestController extends Notifier { + @override + LinkInfo? build() { + final subscription = AppLinks().uriLinkStream.listen((uri) { + if (uri.toString().linkInfo case final linkInfo?) state = linkInfo; + }); + ref.onDispose(subscription.cancel); + + return null; + } + + void consume() => state = null; + + static final provider = NotifierProvider( + LinkRequestController.new, + ); +} diff --git a/lib/helpers/extensions/open_link_info.dart b/lib/helpers/extensions/open_link_info.dart new file mode 100644 index 0000000..5283be1 --- /dev/null +++ b/lib/helpers/extensions/open_link_info.dart @@ -0,0 +1,49 @@ +import "package:material_ui/material_ui.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/event.dart"; +import "package:nexus/controllers/room_summary.dart"; +import "package:nexus/helpers/extensions/focus_room.dart"; +import "package:nexus/helpers/extensions/get_link_info.dart"; +import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; +import "package:nexus/helpers/extensions/show_user_popover.dart"; +import "package:nexus/models/room_summary.dart"; +import "package:nexus/widgets/room_summary_sheet.dart"; + +extension OpenLinkInfo on WidgetRef { + Future openLinkInfo( + BuildContext context, + LinkInfo linkInfo, { + String? roomId, + }) async { + final identifier = linkInfo.identifier; + if (identifier.startsWith("@")) { + context.showUserPopover(identifier, roomId: roomId); + return; + } + + final request = RoomSummaryController.provider( + .new(roomIdOrAlias: identifier, via: linkInfo.via), + ); + final summary = await read(request.future) + .onError((_, _) => RoomSummary(roomId: identifier)); + + final eventRowId = linkInfo.eventId == null + ? null + : await read( + EventController.provider( + .new(roomId: summary.roomId, eventId: linkInfo.eventId!), + ).selectAsync((data) => data?.rowId), + ).onError((_, _) => null); + + if (await focusRoom(summary.roomId, eventRowId) || !context.mounted) return; + + await context.showAdaptiveSheet( + (context) => Consumer( + builder: (context, ref, _) => RoomSummarySheet( + ref.watch(request).whenOrNull(data: (data) => data) ?? summary, + via: linkInfo.via, + ), + ), + ); + } +} diff --git a/lib/widgets/html/mention_chip.dart b/lib/widgets/html/mention_chip.dart index 6ae2221..d6c7c0d 100644 --- a/lib/widgets/html/mention_chip.dart +++ b/lib/widgets/html/mention_chip.dart @@ -1,15 +1,11 @@ import "package:material_ui/material_ui.dart"; -import "package:nexus/helpers/extensions/show_adaptive_sheet.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:nexus/controllers/event.dart"; import "package:nexus/controllers/room_summary.dart"; import "package:nexus/controllers/user.dart"; -import "package:nexus/helpers/extensions/focus_room.dart"; import "package:nexus/helpers/extensions/get_link_info.dart"; -import "package:nexus/helpers/extensions/show_user_popover.dart"; +import "package:nexus/helpers/extensions/open_link_info.dart"; import "package:nexus/models/content/membership.dart"; import "package:nexus/models/room_summary.dart"; -import "package:nexus/widgets/room_summary_sheet.dart"; class const MentionChip(final String content, final String? roomId, {super.key}) extends ConsumerWidget { @@ -28,7 +24,9 @@ class const MentionChip(final String content, final String? roomId, {super.key}) "#" || "!" => ref .watch( - RoomSummaryController.provider(.new(roomIdOrAlias: mention!)), + RoomSummaryController.provider( + .new(roomIdOrAlias: mention!, via: linkInfo!.via), + ), ) .whenOrNull(data: (data) => data) ?? RoomSummary(roomId: mention), @@ -39,43 +37,7 @@ class const MentionChip(final String content, final String? roomId, {super.key}) return mention == null ? SizedBox.shrink() : InkWell( - onTap: () async { - if (data is MembershipContent) { - context.showUserPopover(mention, roomId: roomId); - } else if (data case RoomSummary summary) { - final eventRowId = linkInfo?.eventId == null - ? null - : await ref - .read( - EventController.provider( - .new( - roomId: summary.roomId, - eventId: linkInfo!.eventId!, - ), - ).selectAsync((data) => data?.rowId), - ) - .onError((_, _) => null); - - if (!await ref.focusRoom(summary.roomId, eventRowId) && - context.mounted) { - context.showAdaptiveSheet( - (context) => Consumer( - builder: (context, ref, _) => RoomSummarySheet( - ref - .watch( - RoomSummaryController.provider( - .new(roomIdOrAlias: mention), - ), - ) - .whenOrNull(data: (data) => data) ?? - summary, - via: linkInfo?.via, - ), - ), - ); - } - } - }, + onTap: () => ref.openLinkInfo(context, linkInfo!, roomId: roomId), child: Text( switch (data) { RoomSummary summary => diff --git a/lib/widgets/pages/chat.dart b/lib/widgets/pages/chat.dart index 9e18da7..f7254f9 100644 --- a/lib/widgets/pages/chat.dart +++ b/lib/widgets/pages/chat.dart @@ -2,6 +2,9 @@ import "package:flutter_hooks/flutter_hooks.dart"; 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/link_request.dart"; +import "package:nexus/helpers/extensions/open_link_info.dart"; +import "package:nexus/main.dart"; import "package:nexus/controllers/selected_room.dart"; import "package:nexus/controllers/notification_request.dart"; import "package:nexus/helpers/extensions/is_desktop.dart"; @@ -33,6 +36,23 @@ class const ChatPage({super.key}) extends HookConsumerWidget { return null; }, [notificationRequest, initComplete]); + final linkRequest = ref.watch(LinkRequestController.provider); + + // Open Matrix links the OS launched Nexus with, once sync has completed + useEffect(() { + if (linkRequest == null || !initComplete) return null; + + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(LinkRequestController.provider.notifier).consume(); + navigatorKey.currentState?.popUntil((route) => route.isFirst); + final navigatorContext = navigatorKey.currentContext; + if (navigatorContext == null) return; + + ref.openLinkInfo(navigatorContext, linkRequest).onError(showError); + }); + return null; + }, [linkRequest, initComplete]); + return LayoutBuilder( builder: (context, constraints) { final isDesktop = context.isDesktop; diff --git a/linux/nexus.federated.nexus.uri.desktop b/linux/nexus.federated.nexus.uri.desktop index 10f495f..275c352 100644 --- a/linux/nexus.federated.nexus.uri.desktop +++ b/linux/nexus.federated.nexus.uri.desktop @@ -5,4 +5,4 @@ Icon=nexus Terminal=false Type=Application NoDisplay=true -MimeType=x-scheme-handler/nexus.federated.nexus; \ No newline at end of file +MimeType=x-scheme-handler/nexus.federated.nexus;x-scheme-handler/matrix; \ No newline at end of file diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 36999ec..efc4e8d 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -38,6 +38,7 @@ nexus.federated.nexus + matrix diff --git a/windows/installer.iss b/windows/installer.iss index 22f4dcf..65f1ce5 100644 --- a/windows/installer.iss +++ b/windows/installer.iss @@ -18,4 +18,6 @@ Name: "{commondesktop}\Nexus"; Filename: "{app}\nexus.exe" [Registry] Root: HKCU; Subkey: "Software\Classes\nexus.federated.nexus"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" -Root: HKCU; Subkey: "Software\Classes\nexus.federated.nexus\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\nexus.exe"" ""%1""" \ No newline at end of file +Root: HKCU; Subkey: "Software\Classes\nexus.federated.nexus\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\nexus.exe"" ""%1""" +Root: HKCU; Subkey: "Software\Classes\matrix"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" +Root: HKCU; Subkey: "Software\Classes\matrix\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\nexus.exe"" ""%1"""