mirror of
https://github.com/nostrlabs-io/zap-stream-flutter.git
synced 2025-06-13 19:22:48 +00:00
feat: 1-tap connection
This commit is contained in:
@ -31,6 +31,12 @@
|
||||
<data android:scheme="http" android:host="zap.stream" />
|
||||
<data android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="zswc" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Zap Stream Flutter</string>
|
||||
<string>zap.stream</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -76,5 +76,18 @@
|
||||
<string>vi</string>
|
||||
<string>zh</string>
|
||||
</array>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>zswc</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -29,6 +29,13 @@ void runZapStream() {
|
||||
TextTheme(),
|
||||
),
|
||||
routerConfig: GoRouter(
|
||||
redirect: (context, state) {
|
||||
// redirect back to the wallet settings page
|
||||
if (state.uri.scheme == "zswc") {
|
||||
return "/settings/wallet";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
ShellRoute(
|
||||
observers: [routeObserver],
|
||||
|
@ -35,6 +35,7 @@ const defaultRelays = [
|
||||
"wss://relay.fountain.fm",
|
||||
];
|
||||
const searchRelays = ["wss://relay.nostr.band", "wss://search.nos.today"];
|
||||
const nwcRelays = ["wss://relay.getalby.com/v1"];
|
||||
|
||||
final loginData = LoginData();
|
||||
final RouteObserver<ModalRoute<void>> routeObserver =
|
||||
|
@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 28
|
||||
/// Strings: 1988 (71 per locale)
|
||||
/// Strings: 1991 (71 per locale)
|
||||
///
|
||||
/// Built on 2025-05-26 at 12:50 UTC
|
||||
/// Built on 2025-05-26 at 15:39 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
@ -285,8 +285,10 @@ class TranslationsSettingsWalletEn {
|
||||
final Translations _root; // ignore: unused_field
|
||||
|
||||
// Translations
|
||||
String get connect_wallet => 'Connect Wallet (NWC nwc://)';
|
||||
String get connect_wallet => 'Connect Wallet (NWC nostr+walletconnect://)';
|
||||
String get disconnect_wallet => 'Disconnect Wallet';
|
||||
String get connect_1tap => '1-Tap Connection';
|
||||
String get paste => 'Paste URL';
|
||||
late final TranslationsSettingsWalletErrorEn error = TranslationsSettingsWalletErrorEn.internal(_root);
|
||||
}
|
||||
|
||||
@ -366,6 +368,7 @@ class TranslationsSettingsWalletErrorEn {
|
||||
|
||||
// Translations
|
||||
String get logged_out => 'Cant connect wallet when logged out';
|
||||
String get nwc_auth_event_not_found => 'No wallet auth event found';
|
||||
}
|
||||
|
||||
/// Flat map(s) containing all translations.
|
||||
@ -449,9 +452,12 @@ extension on Translations {
|
||||
case 'settings.profile.nip05': return 'Nostr Address';
|
||||
case 'settings.profile.lud16': return 'Lightning Address';
|
||||
case 'settings.profile.error.logged_out': return 'Cant edit profile when logged out';
|
||||
case 'settings.wallet.connect_wallet': return 'Connect Wallet (NWC nwc://)';
|
||||
case 'settings.wallet.connect_wallet': return 'Connect Wallet (NWC nostr+walletconnect://)';
|
||||
case 'settings.wallet.disconnect_wallet': return 'Disconnect Wallet';
|
||||
case 'settings.wallet.connect_1tap': return '1-Tap Connection';
|
||||
case 'settings.wallet.paste': return 'Paste URL';
|
||||
case 'settings.wallet.error.logged_out': return 'Cant connect wallet when logged out';
|
||||
case 'settings.wallet.error.nwc_auth_event_not_found': return 'No wallet auth event found';
|
||||
case 'login.username': return 'Username';
|
||||
case 'login.amber': return 'Login with Amber';
|
||||
case 'login.key': return 'Login with Key';
|
||||
|
@ -120,10 +120,13 @@ settings:
|
||||
error:
|
||||
logged_out: Cant edit profile when logged out
|
||||
wallet:
|
||||
connect_wallet: Connect Wallet (NWC nwc://)
|
||||
connect_wallet: Connect Wallet (NWC nostr+walletconnect://)
|
||||
disconnect_wallet: Disconnect Wallet
|
||||
connect_1tap: 1-Tap Connection
|
||||
paste: Paste URL
|
||||
error:
|
||||
logged_out: Cant connect wallet when logged out
|
||||
nwc_auth_event_not_found: No wallet auth event found
|
||||
login:
|
||||
username: "Username"
|
||||
amber: Login with Amber
|
||||
|
@ -1,5 +1,14 @@
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:ndk/shared/nips/nip01/bip340.dart';
|
||||
import 'package:ndk/shared/nips/nip01/key_pair.dart';
|
||||
import 'package:protocol_handler/protocol_handler.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:zap_stream_flutter/const.dart';
|
||||
import 'package:zap_stream_flutter/i18n/strings.g.dart';
|
||||
import 'package:zap_stream_flutter/login.dart';
|
||||
@ -13,16 +22,93 @@ class SettingsWalletPage extends StatefulWidget {
|
||||
State<StatefulWidget> createState() => _Inner();
|
||||
}
|
||||
|
||||
class _Inner extends State<SettingsWalletPage> {
|
||||
const nwaHandlerUrl = "zswc://handler";
|
||||
|
||||
class _Inner extends State<SettingsWalletPage> with ProtocolListener {
|
||||
late final TextEditingController _uri;
|
||||
String? _error;
|
||||
|
||||
KeyPair? _nwaKey;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_uri = TextEditingController();
|
||||
protocolHandler.addListener(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
protocolHandler.removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void onProtocolUrlReceived(String url) async {
|
||||
developer.log("NWA: $url");
|
||||
|
||||
if (url == nwaHandlerUrl && _nwaKey != null) {
|
||||
final walletInfos = ndk.requests
|
||||
.query(
|
||||
filters: [
|
||||
Filter(kinds: [13194], pTags: [_nwaKey!.publicKey], limit: 5),
|
||||
],
|
||||
explicitRelays: nwcRelays,
|
||||
)
|
||||
.stream
|
||||
.timeout(Duration(seconds: 15));
|
||||
|
||||
final walletInfo =
|
||||
(await walletInfos.toList())
|
||||
.sortedBy((e) => e.createdAt)
|
||||
.reversed
|
||||
.firstOrNull;
|
||||
if (walletInfo == null) {
|
||||
setState(() {
|
||||
_error = t.settings.wallet.error.nwc_auth_event_not_found;
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
final nwcUrl = Uri(
|
||||
scheme: "nostr+walletconnect",
|
||||
host: walletInfo.pubKey,
|
||||
queryParameters: {"relay": nwcRelays, "secret": _nwaKey!.privateKey},
|
||||
);
|
||||
_setWallet(WalletConfig(type: WalletType.nwc, data: nwcUrl.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _start1TapFlow() async {
|
||||
final key = Bip340.generatePrivateKey();
|
||||
final url = Uri(
|
||||
scheme: "nostr+walletauth",
|
||||
host: key.publicKey,
|
||||
queryParameters: {
|
||||
"relay": nwcRelays,
|
||||
"name": "zap.stream",
|
||||
"request_methods": "pay_invoice",
|
||||
"icon": "https://zap.stream/logo.png",
|
||||
"return_to": nwaHandlerUrl,
|
||||
},
|
||||
);
|
||||
setState(() {
|
||||
_error = null;
|
||||
_nwaKey = key;
|
||||
});
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
|
||||
_setWallet(WalletConfig? cfg) {
|
||||
loginData.value = LoginAccount(
|
||||
type: loginData.value!.type,
|
||||
pubkey: loginData.value!.pubkey,
|
||||
privateKey: loginData.value!.privateKey,
|
||||
signerRelays: loginData.value!.signerRelays,
|
||||
wallet: cfg,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pubkey = ndk.accounts.getPublicKey();
|
||||
@ -33,11 +119,34 @@ class _Inner extends State<SettingsWalletPage> {
|
||||
builder: (context, state, child) {
|
||||
if (state?.wallet == null) {
|
||||
return Column(
|
||||
spacing: 8,
|
||||
spacing: 16,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (Platform.isAndroid) ...[
|
||||
Text(
|
||||
t.settings.wallet.connect_1tap,
|
||||
style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
|
||||
),
|
||||
BasicButton.text(
|
||||
t.button.connect,
|
||||
onTap: (context) {
|
||||
_start1TapFlow().onError((e, _) {
|
||||
setState(() {
|
||||
_error = e.toString();
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
Text(
|
||||
"Paste URL",
|
||||
style: TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextField(
|
||||
controller: _uri,
|
||||
decoration: InputDecoration(labelText: t.settings.wallet.connect_wallet),
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.wallet.connect_wallet,
|
||||
),
|
||||
),
|
||||
BasicButton.text(
|
||||
t.button.connect,
|
||||
@ -48,13 +157,8 @@ class _Inner extends State<SettingsWalletPage> {
|
||||
type: WalletType.nwc,
|
||||
data: _uri.text,
|
||||
);
|
||||
loginData.value = LoginAccount(
|
||||
type: loginData.value!.type,
|
||||
pubkey: loginData.value!.pubkey,
|
||||
privateKey: loginData.value!.privateKey,
|
||||
signerRelays: loginData.value!.signerRelays,
|
||||
wallet: cfg,
|
||||
);
|
||||
_setWallet(cfg);
|
||||
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
@ -73,13 +177,7 @@ class _Inner extends State<SettingsWalletPage> {
|
||||
return BasicButton.text(
|
||||
t.settings.wallet.disconnect_wallet,
|
||||
onTap: (context) {
|
||||
loginData.value = LoginAccount(
|
||||
type: loginData.value!.type,
|
||||
pubkey: loginData.value!.pubkey,
|
||||
privateKey: loginData.value!.privateKey,
|
||||
signerRelays: loginData.value!.signerRelays,
|
||||
wallet: null,
|
||||
);
|
||||
_setWallet(null);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import flutter_secure_storage_macos
|
||||
import objectbox_flutter_libs
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import protocol_handler_macos
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
@ -31,6 +32,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ProtocolHandlerMacosPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerMacosPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
56
pubspec.lock
56
pubspec.lock
@ -896,6 +896,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
protocol_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: protocol_handler
|
||||
sha256: dc2e2dcb1e0e313c3f43827ec3fa6d98adee6e17edc0c3923ac67efee87479a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
protocol_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protocol_handler_android
|
||||
sha256: "82eb860ca42149e400328f54b85140329a1766d982e94705b68271f6ca73895c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
protocol_handler_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protocol_handler_ios
|
||||
sha256: "0d3a56b8c1926002cb1e32b46b56874759f4dcc8183d389b670864ac041b6ec2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
protocol_handler_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protocol_handler_macos
|
||||
sha256: "6eb8687a84e7da3afbc5660ce046f29d7ecf7976db45a9dadeae6c87147dd710"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
protocol_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protocol_handler_platform_interface
|
||||
sha256: "53776b10526fdc25efdf1abcf68baf57fdfdb75342f4101051db521c9e3f3e5b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
protocol_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protocol_handler_windows
|
||||
sha256: d8f3a58938386aca2c76292757392f4d059d09f11439d6d896d876ebe997f2c4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1373,6 +1421,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.0"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.5"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -44,6 +44,7 @@ dependencies:
|
||||
http: ^1.4.0
|
||||
flutter_local_notifications: ^19.2.1
|
||||
flutter_dotenv: ^5.2.1
|
||||
protocol_handler: ^0.2.0
|
||||
|
||||
dependency_overrides:
|
||||
ndk:
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
@ -25,6 +26,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
ObjectboxFlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
|
||||
ProtocolHandlerWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
firebase_core
|
||||
flutter_secure_storage_windows
|
||||
objectbox_flutter_libs
|
||||
protocol_handler_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
Reference in New Issue
Block a user