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 )