diff --git a/android/app/build.gradle b/android/app/build.gradle
index 386e946..2c2dca7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -29,7 +29,7 @@ if (flutterVersionName == null) {
android {
namespace = "com.github.haorendashu.nowser"
- compileSdk = flutter.compileSdkVersion
+ compileSdk = 36
ndkVersion = "28.0.13004108"
// compileOptions {
@@ -57,7 +57,7 @@ android {
// minSdk = flutter.minSdkVersion
// targetSdk = flutter.targetSdkVersion
minSdkVersion flutter.minSdkVersion
- targetSdk = 33
+ targetSdk = 36
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
@@ -83,4 +83,4 @@ android {
flutter {
source = "../.."
-}
+}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 8f10924..c1873d8 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -22,6 +22,8 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
+
+
@@ -32,12 +34,6 @@
-
-
-
-
-
-
diff --git a/android/build.gradle b/android/build.gradle
index 1317df8..c9cb92c 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,3 +1,15 @@
+buildscript {
+ ext.kotlin_version = '2.1.0'
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.13.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
allprojects {
repositories {
google()
@@ -13,8 +25,8 @@ subprojects {
if (namespace == null) {
namespace project.group
}
- compileSdkVersion 34
- buildToolsVersion "34.0.0"
+ compileSdkVersion 36
+ buildToolsVersion "36.0.0"
}
}
}
@@ -28,4 +40,4 @@ subprojects {
tasks.register("clean", Delete) {
delete rootProject.buildDir
-}
+}
\ No newline at end of file
diff --git a/android/gradle.properties b/android/gradle.properties
index 2e65647..b1e7320 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -5,3 +5,6 @@ android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
kotlin.jvm.target.validation.mode = IGNORE
+org.gradle.parallel=true
+org.gradle.configureondemand=true
+android.enableR8=true
\ No newline at end of file
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index dedd5d1..09663cf 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
+zipStorePath=wrapper/dists
\ No newline at end of file
diff --git a/android/settings.gradle b/android/settings.gradle
index dbf9ff3..9c0219e 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.7.0" apply false
+ id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
}
diff --git a/lib/const/app_type.dart b/lib/const/app_type.dart
index 4d2e32a..5851b94 100644
--- a/lib/const/app_type.dart
+++ b/lib/const/app_type.dart
@@ -4,4 +4,6 @@ class AppType {
static const REMOTE = 2;
static const ANDROID_APP = 3;
+
+ static const URI = 4;
}
diff --git a/lib/main.dart b/lib/main.dart
index 19606a2..0c25900 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -15,6 +15,7 @@ import 'package:nostr_sdk/utils/platform_util.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/data/db.dart';
import 'package:nowser/provider/android_signer_mixin.dart';
+import 'package:nowser/provider/app_links_service.dart';
import 'package:nowser/provider/app_provider.dart';
import 'package:nowser/provider/bookmark_provider.dart';
import 'package:nowser/provider/build_in_relay_provider.dart';
@@ -85,6 +86,8 @@ late UserinfoProvider userinfoProvider;
late RelayProvider relayProvider;
+late AppLinksService appLinksService;
+
Nostr? nostr;
Future main() async {
@@ -160,6 +163,7 @@ Future doInit() async {
webProvider = WebProvider();
downloadProvider = DownloadProvider();
await downloadProvider.init();
+ appLinksService = AppLinksService();
}
class MyApp extends StatefulWidget {
diff --git a/lib/provider/app_links_service.dart b/lib/provider/app_links_service.dart
new file mode 100644
index 0000000..d5f8554
--- /dev/null
+++ b/lib/provider/app_links_service.dart
@@ -0,0 +1,133 @@
+import 'dart:convert';
+import 'dart:developer';
+
+import 'package:app_links/app_links.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:nostr_sdk/event.dart';
+import 'package:nostr_sdk/utils/string_util.dart';
+import 'package:nowser/const/app_type.dart';
+import 'package:nowser/const/auth_type.dart';
+import 'package:nowser/provider/permission_check_mixin.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class AppLinksService with PermissionCheckMixin {
+ static const UNKNOWN_CODE = "unknown";
+
+ var appLinks = AppLinks();
+
+ BuildContext? context;
+
+ void updateContext(BuildContext _context) {
+ context = _context;
+ }
+
+ void listen() {
+ appLinks.uriLinkStream.listen(handleUri);
+ }
+
+ void handleUri(Uri uri) {
+ var callbackUrl = uri.queryParameters["callbackUrl"];
+ var type = uri.queryParameters["type"];
+ // var compressionType = uri.queryParameters["compressionType"]; // ignore compressionType now
+ // var returnType = uri.queryParameters["returnType"]; // ignore returnType now
+
+ // intent call also will call this method, if there isn't a type param skip the next handle and it may be a intent call.
+ if (StringUtil.isBlank(type) || !uri.isScheme('nostrsigner')) {
+ return;
+ }
+ log("AppLinksService $uri");
+
+ var appType = AppType.URI;
+ callbackUrl ??= UNKNOWN_CODE;
+ var code = callbackUrl;
+
+ int? eventKind;
+ String? authDetail;
+ String? thirdPartyPubkey;
+ int authType = AuthType.GET_PUBLIC_KEY;
+ dynamic eventObj;
+
+ if (type == "sign_event") {
+ authType = AuthType.SIGN_EVENT;
+ authDetail = uri.host;
+
+ eventObj = jsonDecode(authDetail);
+ eventKind = eventObj["kind"];
+ } else if (type == "get_relays") {
+ authType = AuthType.GET_RELAYS;
+ } else if (type == "get_public_key") {
+ authType = AuthType.GET_PUBLIC_KEY;
+ } else if (type == "nip04_encrypt") {
+ authType = AuthType.NIP04_ENCRYPT;
+ thirdPartyPubkey = uri.queryParameters["pubkey"];
+ authDetail = uri.host;
+ } else if (type == "nip04_decrypt") {
+ authType = AuthType.NIP04_DECRYPT;
+ thirdPartyPubkey = uri.queryParameters["pubkey"];
+ authDetail = uri.host;
+ } else if (type == "nip44_encrypt") {
+ authType = AuthType.NIP44_ENCRYPT;
+ thirdPartyPubkey = uri.queryParameters["pubkey"];
+ authDetail = uri.host;
+ } else if (type == "nip44_decrypt") {
+ authType = AuthType.NIP44_DECRYPT;
+ thirdPartyPubkey = uri.queryParameters["pubkey"];
+ authDetail = uri.host;
+ }
+
+ checkPermission(context!, appType, code, authType,
+ eventKind: eventKind, authDetail: authDetail, (app, rejectType) {
+ // TODO How to return a reject message to app ?
+ return;
+ }, (app, signer) async {
+ String? response;
+
+ if (type == "sign_event") {
+ var tags = eventObj["tags"];
+ Event? event = Event(
+ app.pubkey!, eventObj["kind"], tags ?? [], eventObj["content"],
+ createdAt: eventObj["created_at"]);
+ log(jsonEncode(event.toJson()));
+ event = await signer.signEvent(event);
+ if (event == null) {
+ log("sign event fail");
+ return;
+ }
+
+ response = event.sig;
+ } else if (type == "get_relays") {
+ response = '{}';
+ } else if (type == "get_public_key") {
+ response = await signer.getPublicKey();
+ } else if (type == "nip04_encrypt") {
+ response = await signer.encrypt(thirdPartyPubkey, authDetail);
+ } else if (type == "nip04_decrypt") {
+ response = await signer.decrypt(thirdPartyPubkey, authDetail);
+ } else if (type == "nip44_encrypt") {
+ response = await signer.nip44Encrypt(thirdPartyPubkey, authDetail);
+ } else if (type == "nip44_decrypt") {
+ response = await signer.nip44Decrypt(thirdPartyPubkey, authDetail);
+ }
+
+ sendResponse(callbackUrl!, response);
+ });
+ }
+
+ Future sendResponse(String callbackUrl, String? response) async {
+ if (StringUtil.isBlank(response)) {
+ return;
+ }
+
+ if (callbackUrl == UNKNOWN_CODE) {
+ Clipboard.setData(ClipboardData(text: response!));
+ return;
+ }
+
+ var url = callbackUrl + response!;
+ var uri = Uri.parse(url);
+ if (await canLaunchUrl(uri)) {
+ await launchUrl(uri);
+ }
+ }
+}
diff --git a/lib/router/index/index_router.dart b/lib/router/index/index_router.dart
index 324e817..c70e0e9 100644
--- a/lib/router/index/index_router.dart
+++ b/lib/router/index/index_router.dart
@@ -66,6 +66,8 @@ class _IndexRouter extends CustState
webProvider.checkAndOpenUrl(shortcutType);
});
}
+
+ appLinksService.listen();
}
@override
@@ -96,6 +98,7 @@ class _IndexRouter extends CustState
// });
// }
remoteSigningProvider.updateContext(context);
+ appLinksService.updateContext(context);
webProvider.checkBlank();
var _webProvider = Provider.of(context);
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index 1e1c195..b463d55 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -9,10 +9,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#include
void fl_register_plugins(FlPluginRegistry* registry) {
@@ -25,6 +27,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_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) isar_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
@@ -37,6 +42,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
+ g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+ url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 01ab225..f04cdb5 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -6,10 +6,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_libserialport
flutter_nesigner_sdk
flutter_secure_storage_linux
+ gtk
isar_flutter_libs
nesigner_adapter
open_file_linux
screen_retriever_linux
+ url_launcher_linux
window_manager
)
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 7ddef2c..b9ffc8b 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
+import app_links
import cryptography_flutter
import device_info_plus
import flutter_inappwebview_macos
@@ -18,9 +19,11 @@ import path_provider_foundation
import screen_retriever_macos
import shared_preferences_foundation
import sqflite_darwin
+import url_launcher_macos
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
CryptographyFlutterPlugin.register(with: registry.registrar(forPlugin: "CryptographyFlutterPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
@@ -34,5 +37,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
+ UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 9c5696d..63241ff 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -9,6 +9,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.2"
+ app_links:
+ dependency: "direct main"
+ description:
+ name: app_links
+ sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.4.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:
@@ -502,6 +534,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.1"
+ gtk:
+ dependency: transitive
+ description:
+ name: gtk
+ sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
hex:
dependency: transitive
description:
@@ -943,11 +983,12 @@ packages:
receive_intent:
dependency: "direct main"
description:
- name: receive_intent
- sha256: "59afac5bcac8a0c6fc5067d03b4daeb260399cc8485256d6fe40a26640512cda"
- url: "https://pub.dev"
- source: hosted
- version: "0.2.5"
+ path: "."
+ ref: master
+ resolved-ref: "471e074120ce7bdffe39e351af0c8d13d1bcd64c"
+ url: "https://github.com/daadu/receive_intent"
+ source: git
+ version: "0.2.7"
relay_isar_db:
dependency: transitive
description:
@@ -1231,6 +1272,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.1"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.28"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.6"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.5"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.5"
usb_serial:
dependency: transitive
description:
@@ -1336,5 +1441,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
- dart: ">=3.8.0 <4.0.0"
- flutter: ">=3.29.0"
+ dart: ">=3.9.0 <4.0.0"
+ flutter: ">=3.35.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 9017f75..7df4039 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -42,7 +42,10 @@ dependencies:
nesigner_adapter:
path: packages/nesigner_adapter
- receive_intent: ^0.2.5
+ receive_intent:
+ git:
+ url: https://github.com/daadu/receive_intent
+ ref: master
provider: ^6.1.2
flutter_inappwebview: ^6.1.5
bot_toast: ^4.1.3
@@ -67,6 +70,8 @@ dependencies:
path_provider: ^2.1.5
background_downloader: ^9.2.1
open_file: ^3.5.10
+ app_links: 6.4.0
+ url_launcher: 6.2.1
dev_dependencies:
flutter_launcher_icons: ^0.13.1
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index de861ea..af4b8e0 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
@@ -13,9 +14,12 @@
#include
#include
#include
+#include
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ AppLinksPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterLibserialportPluginRegisterWithRegistrar(
@@ -30,6 +34,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("NesignerAdapterPluginCApi"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
+ UrlLauncherWindowsRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
}
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 4258c7e..edf9c9a 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ app_links
flutter_inappwebview_windows
flutter_libserialport
flutter_nesigner_sdk
@@ -10,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
isar_flutter_libs
nesigner_adapter
screen_retriever_windows
+ url_launcher_windows
window_manager
)