diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 666977e..a0a0bcc 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -22,6 +22,14 @@
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
+
+
+
+
+
+
+
+
{
.getAuthUrl(
.new(
homeserverUrl: homeserver,
- redirectUri: .new(scheme: "nexus.federated.nexus"),
+ redirectUri: .new(scheme: "nexus.federated.nexus", path: "/"),
responseMode: .query,
scopes: .new([.clientApi]),
clientId: await ref.watch(
diff --git a/lib/controllers/client.dart b/lib/controllers/client.dart
index 28c4d08..cd67a62 100644
--- a/lib/controllers/client.dart
+++ b/lib/controllers/client.dart
@@ -22,6 +22,7 @@ import "package:nexus/models/requests/get_related_events.dart";
import "package:nexus/models/requests/get_room_state.dart";
import "package:nexus/models/requests/join_room.dart";
import "package:nexus/models/profile.dart";
+import "package:nexus/models/requests/oauth/exchange_token.dart";
import "package:nexus/models/requests/oauth/get_auth_url.dart";
import "package:nexus/models/requests/oauth/register_client.dart";
import "package:nexus/models/requests/paginate.dart";
@@ -288,6 +289,9 @@ class ClientController extends AsyncNotifier {
await _sendCommand("oauth_get_authorization_url", request.toJson()),
);
+ Future exchangeToken(OAuthExchangeTokenRequest request) async =>
+ await _sendCommand("oauth_exchange_token", request.toJson());
+
Future discoverHomeserver(Uri homeserver) async {
try {
final response = await _sendCommand("discover_homeserver", {
diff --git a/lib/controllers/client_id.dart b/lib/controllers/client_id.dart
index 8be30f2..ac8f5cb 100644
--- a/lib/controllers/client_id.dart
+++ b/lib/controllers/client_id.dart
@@ -20,7 +20,9 @@ class ClientIdController extends AsyncNotifier {
),
homeserverUrl: homeserver,
clientUri: Uri.https("nexus.federated.nexus"),
- redirectUris: .new([.new(scheme: "nexus.federated.nexus")]),
+ redirectUris: .new([
+ .new(scheme: "nexus.federated.nexus", path: "/"),
+ ]),
),
);
diff --git a/lib/main.dart b/lib/main.dart
index df3ed91..05731e4 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -79,6 +79,7 @@ void main() async {
runApp(
ProviderScope(
+ retry: null,
observers: [
// Change false to true if you want debug information on provider reloads
// ignore: dead_code
diff --git a/lib/models/requests/oauth/exchange_token.dart b/lib/models/requests/oauth/exchange_token.dart
new file mode 100644
index 0000000..9d55c1f
--- /dev/null
+++ b/lib/models/requests/oauth/exchange_token.dart
@@ -0,0 +1,17 @@
+import "package:freezed_annotation/freezed_annotation.dart";
+part "exchange_token.freezed.dart";
+part "exchange_token.g.dart";
+
+@freezed
+abstract class OAuthExchangeTokenRequest with _$OAuthExchangeTokenRequest {
+ const factory OAuthExchangeTokenRequest({
+ required Uri homeserverUrl,
+ required String codeVerifier,
+ required Uri redirectUri,
+ required String code,
+ required String clientId,
+ }) = _OAuthExchangeTokenRequest;
+
+ factory OAuthExchangeTokenRequest.fromJson(Map json) =>
+ _$OAuthExchangeTokenRequestFromJson(json);
+}
diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart
index 235aecc..fdd3763 100644
--- a/lib/pages/chat.dart
+++ b/lib/pages/chat.dart
@@ -18,6 +18,7 @@ class ChatPage extends ConsumerWidget {
final showMembersByDefault = constraints.maxWidth > 1000;
final initComplete = ref.watch(InitCompleteController.provider);
final roomId = ref.watch(KeyController.provider(KeyController.roomKey));
+
ref.read(EmojiController.provider);
return Scaffold(
diff --git a/lib/pages/select_server.dart b/lib/pages/select_server.dart
index 2d02e28..b4cca9c 100644
--- a/lib/pages/select_server.dart
+++ b/lib/pages/select_server.dart
@@ -1,9 +1,11 @@
+import "package:app_links/app_links.dart";
import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_svg/flutter_svg.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/auth_url.dart";
import "package:nexus/controllers/client.dart";
+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";
@@ -48,11 +50,35 @@ class SelectServerPage extends HookConsumerWidget {
),
);
} else {
- final authUrl = await ref.read(
+ final codeResponse = await ref.watch(
AuthUrlController.provider(newUrl).future,
);
- await ref.watch(LaunchHelper.provider).launchUrl(authUrl.url);
+ await ref.watch(LaunchHelper.provider).launchUrl(codeResponse.url);
+
+ AppLinks().uriLinkStream.listen((encodedUri) async {
+ final code = encodedUri.queryParameters["code"];
+
+ if (code != null) {
+ await ref
+ .watch(ClientController.provider.notifier)
+ .exchangeToken(
+ .new(
+ homeserverUrl: newUrl,
+ codeVerifier: codeResponse.codeVerifier,
+ redirectUri: .new(
+ scheme: "nexus.federated.nexus",
+ path: "/",
+ ),
+ code: code,
+ clientId: await ref.watch(
+ ClientIdController.provider(newUrl).future,
+ ),
+ ),
+ )
+ .onError(showError);
+ }
+ });
// await Navigator.of(context).push(
// MaterialPageRoute(builder: (_) => LoginPage(homeserver: newUrl)),
// );
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index 755a54e..603ea6a 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
#include
@@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
+ g_autoptr(FlPluginRegistrar) gtk_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
+ gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index b9ca03c..7d8f046 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
file_selector_linux
+ gtk
media_kit_libs_linux
media_kit_video
screen_retriever_linux
diff --git a/linux/nexus.federated.Nexus.desktop b/linux/nexus.federated.Nexus.desktop
index d3fa575..d61734e 100644
--- a/linux/nexus.federated.Nexus.desktop
+++ b/linux/nexus.federated.Nexus.desktop
@@ -2,8 +2,9 @@
Name=Nexus
GenericName=Matrix Client
Comment=A simple and user-friendly Matrix client
-Exec=nexus
+Exec=nexus %u
Icon=nexus
Terminal=false
Type=Application
-Categories=Chat;Network;InstantMessaging;
\ No newline at end of file
+Categories=Chat;Network;InstantMessaging;
+MimeType=x-scheme-handler/nexus.federated.nexus;
\ No newline at end of file
diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc
index abf5dc5..62e5450 100644
--- a/linux/runner/my_application.cc
+++ b/linux/runner/my_application.cc
@@ -23,6 +23,13 @@ static void first_frame_cb(MyApplication* self, FlView *view)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
+
+ GList* windows = gtk_application_get_windows(GTK_APPLICATION(application));
+ if (windows) {
+ gtk_window_present(GTK_WINDOW(windows->data));
+ return;
+ }
+
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
@@ -93,7 +100,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch
g_application_activate(application);
*exit_status = 0;
- return TRUE;
+ return FALSE;
}
// Implements GApplication::startup.
@@ -140,6 +147,6 @@ MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
- "flags", G_APPLICATION_NON_UNIQUE,
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
nullptr));
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index c1b7cee..7b5a69f 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import app_links
import dynamic_color
import file_picker
import file_selector_macos
@@ -18,6 +19,7 @@ import wakelock_plus
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist
index 4789daa..36999ec 100644
--- a/macos/Runner/Info.plist
+++ b/macos/Runner/Info.plist
@@ -28,5 +28,18 @@
MainMenu
NSPrincipalClass
NSApplication
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+
+ auth_callback
+ CFBundleURLSchemes
+
+
+ nexus.federated.nexus
+
+
+
diff --git a/pubspec.lock b/pubspec.lock
index 9c978d3..4813bb8 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -41,6 +41,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.13.11"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ sha256: "3462d9defc61565fde4944858b59bec5be2b9d5b05f20aed190adb3ad08a7abc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ app_links_linux:
+ dependency: transitive
+ description:
+ name: app_links_linux
+ sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.3"
+ app_links_platform_interface:
+ dependency: transitive
+ description:
+ name: app_links_platform_interface
+ sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ app_links_web:
+ dependency: transitive
+ description:
+ name: app_links_web
+ sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
archive:
dependency: transitive
description:
@@ -558,6 +590,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
+ gtk:
+ dependency: transitive
+ description:
+ name: gtk
+ sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
hooks:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 98dcf40..33f0ffd 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -76,6 +76,7 @@ dependencies:
m3e_card_list: ^0.1.0
xdg_directories: ^1.1.0
package_info_plus: ^9.0.1
+ app_links: ^7.0.0
dev_dependencies:
build_runner: 2.15.0
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 7938787..0694802 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h"
+#include
#include
#include
#include
@@ -15,6 +16,8 @@
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AppLinksPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 64ef5b5..238ef93 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ app_links
dynamic_color
file_selector_windows
media_kit_libs_windows_video
diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp
index 453f9cf..590e663 100644
--- a/windows/runner/main.cpp
+++ b/windows/runner/main.cpp
@@ -1,12 +1,17 @@
#include
#include
#include
-
+#include "app_links/app_links_plugin_c_api.h"
#include "flutter_window.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
+ // Forward the link to an existing instance, if any, then exit.
+ // You may ignore the result if you need to create another window.
+ if (SendAppLinkToInstance()) {
+ return EXIT_SUCCESS;
+ }
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {