mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
NWC secret in secure storage
This commit is contained in:
@@ -32,9 +32,6 @@ class ProfileScreen extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final signedInPubkey = ref.watch(Signer.activePubkeyProvider);
|
||||
final isSignedIn = signedInPubkey != null;
|
||||
|
||||
return Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
@@ -48,18 +45,15 @@ class ProfileScreen extends ConsumerWidget {
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Settings Heading
|
||||
if (isSignedIn)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text('Settings', style: context.textTheme.headlineSmall),
|
||||
),
|
||||
|
||||
if (isSignedIn) ...[
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Lightning Wallet Section
|
||||
const NWCConnectionCard(),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
/// Service for securely storing sensitive data (NWC connection strings)
|
||||
/// using platform-native secure storage (Keychain on iOS, KeyStore on Android).
|
||||
///
|
||||
/// This does NOT require user authentication - data is encrypted at rest
|
||||
/// by the platform's secure storage mechanism.
|
||||
class SecureStorageService {
|
||||
SecureStorageService();
|
||||
|
||||
static const _storage = FlutterSecureStorage();
|
||||
|
||||
static const _nwcKey = 'nwc_connection_string';
|
||||
|
||||
/// Get the stored NWC connection string
|
||||
Future<String?> getNWCString() async {
|
||||
final value = await _storage.read(key: _nwcKey);
|
||||
return (value?.isNotEmpty == true) ? value : null;
|
||||
}
|
||||
|
||||
/// Store an NWC connection string
|
||||
Future<void> setNWCString(String connectionString) async {
|
||||
await _storage.write(key: _nwcKey, value: connectionString);
|
||||
}
|
||||
|
||||
/// Clear the stored NWC connection string
|
||||
Future<void> clearNWCString() async {
|
||||
await _storage.delete(key: _nwcKey);
|
||||
}
|
||||
|
||||
/// Check if an NWC connection string is stored
|
||||
Future<bool> hasNWCString() async {
|
||||
final value = await _storage.read(key: _nwcKey);
|
||||
return value?.isNotEmpty == true;
|
||||
}
|
||||
}
|
||||
|
||||
final secureStorageServiceProvider = Provider<SecureStorageService>(
|
||||
(ref) => SecureStorageService(),
|
||||
);
|
||||
|
||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:models/models.dart';
|
||||
import 'package:zapstore/services/notification_service.dart';
|
||||
import 'package:zapstore/services/secure_storage_service.dart';
|
||||
import 'package:zapstore/theme.dart';
|
||||
import 'package:zapstore/utils/extensions.dart';
|
||||
|
||||
/// NWC connection status states
|
||||
enum NWCStatus { notSignedIn, checking, connected, disconnected, error }
|
||||
enum NWCStatus { checking, connected, disconnected, error }
|
||||
|
||||
/// Card showing NWC connection status and management
|
||||
class NWCConnectionCard extends HookConsumerWidget {
|
||||
@@ -16,38 +16,18 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final signer = ref.watch(Signer.activeSignerProvider);
|
||||
final pubkey = ref.watch(Signer.activePubkeyProvider);
|
||||
final isSignedIn = pubkey != null;
|
||||
final secureStorage = ref.watch(secureStorageServiceProvider);
|
||||
final nwcStatus = useState<NWCStatus>(NWCStatus.checking);
|
||||
|
||||
// Reactive NWC state via CustomData keyed by Signer.kNwcConnectionString
|
||||
final nwcState = pubkey == null
|
||||
? null
|
||||
: ref.watch(
|
||||
query<CustomData>(
|
||||
authors: {pubkey},
|
||||
tags: {
|
||||
'#d': {Signer.kNwcConnectionString},
|
||||
},
|
||||
limit: 1,
|
||||
source: const LocalSource(),
|
||||
subscriptionPrefix: 'nwc-data',
|
||||
),
|
||||
);
|
||||
|
||||
final NWCStatus nwcStatus = () {
|
||||
if (signer == null || pubkey == null) return NWCStatus.notSignedIn;
|
||||
final state = nwcState;
|
||||
if (state == null) return NWCStatus.checking;
|
||||
return switch (state) {
|
||||
StorageLoading() => NWCStatus.checking,
|
||||
StorageError() => NWCStatus.error,
|
||||
StorageData(:final models) =>
|
||||
(models.isNotEmpty && models.first.content.trim().isNotEmpty)
|
||||
? NWCStatus.connected
|
||||
: NWCStatus.disconnected,
|
||||
};
|
||||
}();
|
||||
// Check NWC status on mount
|
||||
useEffect(() {
|
||||
secureStorage.hasNWCString().then((hasNwc) {
|
||||
nwcStatus.value = hasNwc ? NWCStatus.connected : NWCStatus.disconnected;
|
||||
}).catchError((_) {
|
||||
nwcStatus.value = NWCStatus.error;
|
||||
});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
@@ -73,29 +53,14 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Text(
|
||||
isSignedIn
|
||||
? 'Use Nostr Wallet Connect (NWC) to zap the developers.'
|
||||
: 'Connect a Lightning wallet to send zaps. Sign in required.',
|
||||
'Use Nostr Wallet Connect (NWC) to zap the developers.',
|
||||
style: context.textTheme.bodySmall,
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Connection Status
|
||||
switch (nwcStatus) {
|
||||
NWCStatus.notSignedIn => Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Expanded(child: Text('Sign in to connect wallet')),
|
||||
],
|
||||
),
|
||||
switch (nwcStatus.value) {
|
||||
NWCStatus.checking => Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
@@ -155,14 +120,11 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Action Buttons
|
||||
if (isSignedIn)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton.icon(
|
||||
onPressed: nwcStatus != NWCStatus.notSignedIn
|
||||
? () => _showNWCDialog(context, ref, signer)
|
||||
: null,
|
||||
onPressed: () => _showNWCDialog(context, ref, nwcStatus),
|
||||
icon: Icon(
|
||||
Icons.flash_on,
|
||||
size: 18,
|
||||
@@ -171,7 +133,7 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: Text(
|
||||
nwcStatus == NWCStatus.connected
|
||||
nwcStatus.value == NWCStatus.connected
|
||||
? 'Update Connection'
|
||||
: 'Connect Wallet',
|
||||
),
|
||||
@@ -185,11 +147,11 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (nwcStatus == NWCStatus.connected) ...[
|
||||
if (nwcStatus.value == NWCStatus.connected) ...[
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () =>
|
||||
_removeNWCConnection(context, ref, signer),
|
||||
_removeNWCConnection(context, ref, nwcStatus),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
foregroundColor: Colors.white,
|
||||
@@ -210,22 +172,25 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showNWCDialog(BuildContext context, WidgetRef ref, Signer? signer) {
|
||||
if (signer == null) return;
|
||||
|
||||
showDialog(
|
||||
void _showNWCDialog(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
ValueNotifier<NWCStatus> nwcStatus,
|
||||
) async {
|
||||
final connected = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => NWCConnectionDialog(signer: signer),
|
||||
builder: (context) => NWCConnectionDialog(ref: ref),
|
||||
);
|
||||
if (connected == true) {
|
||||
nwcStatus.value = NWCStatus.connected;
|
||||
}
|
||||
}
|
||||
|
||||
void _removeNWCConnection(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Signer? signer,
|
||||
ValueNotifier<NWCStatus> nwcStatus,
|
||||
) async {
|
||||
if (signer == null) return;
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
@@ -253,7 +218,9 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await signer.setNWCString('');
|
||||
final secureStorage = ref.read(secureStorageServiceProvider);
|
||||
await secureStorage.clearNWCString();
|
||||
nwcStatus.value = NWCStatus.disconnected;
|
||||
if (context.mounted) {
|
||||
context.showInfo('Wallet connection removed');
|
||||
}
|
||||
@@ -268,9 +235,9 @@ class NWCConnectionCard extends HookConsumerWidget {
|
||||
|
||||
/// Dialog for connecting/updating NWC connection
|
||||
class NWCConnectionDialog extends HookWidget {
|
||||
final Signer signer;
|
||||
final WidgetRef ref;
|
||||
|
||||
const NWCConnectionDialog({super.key, required this.signer});
|
||||
const NWCConnectionDialog({super.key, required this.ref});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -367,10 +334,11 @@ class NWCConnectionDialog extends HookWidget {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await signer.setNWCString(nwcString.trim());
|
||||
final secureStorage = ref.read(secureStorageServiceProvider);
|
||||
await secureStorage.setNWCString(nwcString.trim());
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context, true);
|
||||
context.showInfo('⚡ Lightning wallet connected successfully!');
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -382,4 +350,3 @@ class NWCConnectionDialog extends HookWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:models/models.dart';
|
||||
import 'package:zapstore/main.dart';
|
||||
import 'package:zapstore/services/notification_service.dart';
|
||||
import 'package:zapstore/services/package_manager/package_manager.dart';
|
||||
import 'package:zapstore/services/secure_storage_service.dart';
|
||||
import 'package:zapstore/utils/extensions.dart';
|
||||
import 'package:zapstore/widgets/common/base_dialog.dart';
|
||||
import 'package:zapstore/widgets/common/profile_avatar.dart';
|
||||
@@ -217,21 +218,15 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
],
|
||||
);
|
||||
final pubkey = ref.watch(Signer.activePubkeyProvider);
|
||||
final signer = ref.watch(Signer.activeSignerProvider);
|
||||
final secureStorage = ref.watch(secureStorageServiceProvider);
|
||||
final hasWalletConnection = useState<bool>(false);
|
||||
useEffect(() {
|
||||
if (signer == null) {
|
||||
hasWalletConnection.value = false;
|
||||
return null;
|
||||
}
|
||||
signer
|
||||
.getNWCString()
|
||||
.then(
|
||||
(value) => hasWalletConnection.value = (value?.isNotEmpty == true),
|
||||
)
|
||||
secureStorage
|
||||
.hasNWCString()
|
||||
.then((hasNwc) => hasWalletConnection.value = hasNwc)
|
||||
.catchError((_) => hasWalletConnection.value = false);
|
||||
return null;
|
||||
}, [signer]);
|
||||
}, []);
|
||||
|
||||
return BaseDialog(
|
||||
titleIcon: const Text('⚡️'),
|
||||
@@ -363,7 +358,7 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Tap to sign in. You can zap anonymously or sign in for attribution.',
|
||||
'Zapping anonymously. Tap to sign in.',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
@@ -374,14 +369,12 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
if (pubkey != null && !hasWalletConnection.value) ...[
|
||||
if (!hasWalletConnection.value) ...[
|
||||
InkWell(
|
||||
onTap: signer == null
|
||||
? null
|
||||
: () async {
|
||||
onTap: () async {
|
||||
final connected = await showBaseDialog<bool>(
|
||||
context: context,
|
||||
dialog: NWCConnectionDialogInline(signer: signer),
|
||||
dialog: const NWCConnectionDialogInline(),
|
||||
);
|
||||
if (connected == true) {
|
||||
hasWalletConnection.value = true;
|
||||
@@ -478,6 +471,8 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
onPressed: selectedAmount.value > 0
|
||||
? () async {
|
||||
try {
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
// Prepare signer (anonymous if needed)
|
||||
var signer = ref.read(Signer.activeSignerProvider);
|
||||
if (signer == null) {
|
||||
@@ -488,8 +483,8 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
await signer.signIn(registerSigner: false);
|
||||
}
|
||||
|
||||
// Read NWC state (may be empty)
|
||||
final nwcString = await signer.getNWCString();
|
||||
// Read NWC from secure storage
|
||||
final nwcString = await secureStorage.getNWCString();
|
||||
|
||||
// Build zap request
|
||||
final latestMetadata = app.latestFileMetadata;
|
||||
@@ -516,29 +511,33 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
final signedZapRequest = await zapRequest.signWith(signer);
|
||||
|
||||
if (nwcString != null && nwcString.isNotEmpty) {
|
||||
if (context.mounted) Navigator.of(context).pop(true);
|
||||
// Continue payment in background (no await)
|
||||
final amount = selectedAmount.value;
|
||||
// Capture ScaffoldMessenger before popping (survives navigation)
|
||||
final messenger = ScaffoldMessenger.maybeOf(context);
|
||||
|
||||
// Optimistic UX: show success immediately, pop, fire payment
|
||||
context.showInfo('⚡ Zap sent! $amount sats');
|
||||
navigator.pop(true);
|
||||
|
||||
// Fire payment in background - errors shown via ScaffoldMessenger
|
||||
// ignore: unawaited_futures
|
||||
signedZapRequest
|
||||
.pay()
|
||||
.then((_) {
|
||||
if (context.mounted) {
|
||||
context.showInfo(
|
||||
'⚡ Zap successful! ${selectedAmount.value} sats sent',
|
||||
_executeZapPayment(signedZapRequest, nwcString, ref.ref)
|
||||
.catchError((Object e, StackTrace st) {
|
||||
debugPrint('Zap payment failed: $e\n$st');
|
||||
messenger?.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Zap failed: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
})
|
||||
.catchError((e) {
|
||||
if (context.mounted) {
|
||||
context.showError('Zap failed: $e');
|
||||
}
|
||||
return Future<PayInvoiceResult>.error(e, st);
|
||||
});
|
||||
} else {
|
||||
final invoice = await signedZapRequest.getInvoice();
|
||||
if (context.mounted) Navigator.of(context).pop(true);
|
||||
await Clipboard.setData(ClipboardData(text: invoice));
|
||||
if (context.mounted) {
|
||||
context.showInfo('Invoice copied to clipboard');
|
||||
navigator.pop(true);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -579,6 +578,21 @@ class ZapAmountDialog extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute zap payment using NWC connection string from secure storage
|
||||
Future<PayInvoiceResult> _executeZapPayment(
|
||||
ZapRequest signedZapRequest,
|
||||
String nwcString,
|
||||
Ref ref,
|
||||
) async {
|
||||
final lightningInvoice = await signedZapRequest.getInvoice();
|
||||
final command = PayInvoiceCommand(invoice: lightningInvoice);
|
||||
return await command.execute(
|
||||
connectionUri: nwcString,
|
||||
ref: ref,
|
||||
timeout: const Duration(seconds: 30),
|
||||
);
|
||||
}
|
||||
|
||||
/// Dialog for entering a custom zap amount
|
||||
class CustomAmountDialog extends HookWidget {
|
||||
const CustomAmountDialog({super.key});
|
||||
@@ -620,15 +634,14 @@ class CustomAmountDialog extends HookWidget {
|
||||
}
|
||||
|
||||
/// Inline NWC connection dialog used in zap flow
|
||||
class NWCConnectionDialogInline extends HookWidget {
|
||||
final Signer signer;
|
||||
|
||||
const NWCConnectionDialogInline({super.key, required this.signer});
|
||||
class NWCConnectionDialogInline extends HookConsumerWidget {
|
||||
const NWCConnectionDialogInline({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final controller = useTextEditingController();
|
||||
final isLoading = useState(false);
|
||||
final secureStorage = ref.watch(secureStorageServiceProvider);
|
||||
|
||||
return BaseDialog(
|
||||
titleIcon: const Text('⚡️'),
|
||||
@@ -693,7 +706,7 @@ class NWCConnectionDialogInline extends HookWidget {
|
||||
}
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await signer.setNWCString(nwcString.trim());
|
||||
await secureStorage.setNWCString(nwcString.trim());
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
context.showInfo(
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <gtk/gtk_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_secure_storage_linux
|
||||
gtk
|
||||
sqlite3_flutter_libs
|
||||
url_launcher_linux
|
||||
|
||||
@@ -7,6 +7,7 @@ import Foundation
|
||||
|
||||
import app_links
|
||||
import connectivity_plus
|
||||
import flutter_secure_storage_darwin
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
@@ -17,6 +18,7 @@ import url_launcher_macos
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
|
||||
@@ -399,6 +399,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
flutter_secure_storage_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_darwin
|
||||
sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -53,6 +53,7 @@ dependencies:
|
||||
background_downloader: ^9.2.6
|
||||
connectivity_plus: ^7.0.0
|
||||
app_links: ^6.4.1
|
||||
flutter_secure_storage: ^10.0.0
|
||||
|
||||
dependency_overrides:
|
||||
models:
|
||||
|
||||
Reference in New Issue
Block a user