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"""