diff --git a/lib/features/home/home_screen.dart b/lib/features/home/home_screen.dart new file mode 100644 index 0000000..5b58990 --- /dev/null +++ b/lib/features/home/home_screen.dart @@ -0,0 +1,65 @@ +import 'package:bitcoin_ui/bitcoin_ui.dart'; +import 'package:donationwallet/features/wallet/screens/wallet_screen.dart'; +import 'package:donationwallet/features/settings/screens/settings_screen.dart'; +import 'package:flutter/material.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + HomeScreenState createState() => HomeScreenState(); +} + +class HomeScreenState extends State { + int _selectedIndex = 0; + final List _widgetOptions = [ + const WalletScreen(), + const SettingsScreen(), + ]; + + @override + void initState() { + super.initState(); + } + + void _onItemTapped(int index) { + setState(() { + _selectedIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Silent Payments'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: IndexedStack( + index: _selectedIndex, + children: _widgetOptions, + ), + bottomNavigationBar: BottomNavigationBar( + items: [ + BottomNavigationBarItem( + icon: Image( + image: + const AssetImage("icons/wallet.png", package: "bitcoin_ui"), + color: Bitcoin.neutral3Dark), + label: 'Wallet', + ), + BottomNavigationBarItem( + icon: Image( + image: + const AssetImage("icons/gear.png", package: "bitcoin_ui"), + color: Bitcoin.neutral3Dark), + label: 'Settings', + ), + ], + currentIndex: _selectedIndex, + selectedItemColor: Colors.green, + onTap: _onItemTapped, + ), + ); + } +} diff --git a/lib/settings.dart b/lib/features/settings/controllers/settings_controller.dart similarity index 50% rename from lib/settings.dart rename to lib/features/settings/controllers/settings_controller.dart index 8055ba8..6474e7d 100644 --- a/lib/settings.dart +++ b/lib/features/settings/controllers/settings_controller.dart @@ -1,15 +1,13 @@ -import 'package:bitcoin_ui/bitcoin_ui.dart'; import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/global_functions.dart'; -import 'package:donationwallet/main.dart'; -import 'package:donationwallet/home.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; +import 'package:donationwallet/utils/global_functions.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -class SettingsScreen extends StatelessWidget { - const SettingsScreen({super.key}); +class SettingsController { + const SettingsController(); - Future _removeWallet( + Future removeWallet( WalletState walletState, Function(Exception? e) callback) async { try { await walletState.rmWalletFromSecureStorage(); @@ -22,7 +20,7 @@ class SettingsScreen extends StatelessWidget { } } - Future _getSeedPhrase(WalletState walletState) async { + Future getSeedPhrase(WalletState walletState) async { try { final wallet = await walletState.getWalletFromSecureStorage(); return showMnemonic(encodedWallet: wallet); @@ -32,7 +30,7 @@ class SettingsScreen extends StatelessWidget { } } - Future _setBirthday(BuildContext context, + Future setBirthday(BuildContext context, TextEditingController controller, Function(Exception? e) callback) async { showDialog( context: context, @@ -71,9 +69,8 @@ class SettingsScreen extends StatelessWidget { final walletState = Provider.of(context, listen: false); try { final wallet = await walletState.getWalletFromSecureStorage(); - final updatedWallet = changeBirthday( - encodedWallet: wallet, - birthday: value); + final updatedWallet = + changeBirthday(encodedWallet: wallet, birthday: value); walletState.saveWalletToSecureStorage(updatedWallet); callback(null); await walletState.updateWalletStatus(); @@ -85,60 +82,4 @@ class SettingsScreen extends StatelessWidget { } }); } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - BitcoinButtonOutlined( - title: 'Show seed phrase', - onPressed: () async { - final walletState = - Provider.of(context, listen: false); - - const title = 'Backup seed phrase'; - final text = await _getSeedPhrase(walletState) ?? - 'Seed phrase unknown! Did you import from keys?'; - - showAlertDialog(title, text); - }, - ), - BitcoinButtonOutlined( - title: 'Set wallet birthday', - onPressed: () async { - final controller = TextEditingController(); - await _setBirthday(context, controller, (Exception? e) async { - if (e != null) { - throw e; - } else { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const HomeScreen())); - } - }); - }, - ), - BitcoinButtonOutlined( - title: 'Wipe wallet', - onPressed: () async { - final walletState = - Provider.of(context, listen: false); - await _removeWallet(walletState, (Exception? e) async { - if (e != null) { - throw e; - } else { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => const HomeScreen()), - (Route route) => false, - ); - } - }); - }, - ), - ], - ); - } } diff --git a/lib/features/settings/screens/settings_screen.dart b/lib/features/settings/screens/settings_screen.dart new file mode 100644 index 0000000..fb35ac1 --- /dev/null +++ b/lib/features/settings/screens/settings_screen.dart @@ -0,0 +1,73 @@ +import 'package:bitcoin_ui/bitcoin_ui.dart'; +import 'package:donationwallet/features/settings/controllers/settings_controller.dart'; +import 'package:donationwallet/features/setup/screens/setup_wallet_screen.dart'; +import 'package:donationwallet/utils/global_functions.dart'; +import 'package:donationwallet/features/home/home_screen.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SettingsScreen extends StatelessWidget { + final settingsController = const SettingsController(); + + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + BitcoinButtonOutlined( + title: 'Show seed phrase', + onPressed: () async { + final walletState = + Provider.of(context, listen: false); + + const title = 'Backup seed phrase'; + final text = await settingsController.getSeedPhrase(walletState) ?? + 'Seed phrase unknown! Did you import from keys?'; + + showAlertDialog(title, text); + }, + ), + BitcoinButtonOutlined( + title: 'Set wallet birthday', + onPressed: () async { + final controller = TextEditingController(); + await settingsController.setBirthday(context, controller, + (Exception? e) async { + if (e != null) { + throw e; + } else { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const HomeScreen())); + } + }); + }, + ), + BitcoinButtonOutlined( + title: 'Wipe wallet', + onPressed: () async { + final walletState = + Provider.of(context, listen: false); + await settingsController.removeWallet(walletState, + (Exception? e) async { + if (e != null) { + throw e; + } else { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => const SetupWalletScreen()), + (Route route) => false, + ); + } + }); + }, + ), + ], + ); + } +} diff --git a/lib/load_wallet.dart b/lib/features/setup/screens/setup_wallet_screen.dart similarity index 96% rename from lib/load_wallet.dart rename to lib/features/setup/screens/setup_wallet_screen.dart index 64ee70d..b926474 100644 --- a/lib/load_wallet.dart +++ b/lib/features/setup/screens/setup_wallet_screen.dart @@ -1,14 +1,14 @@ import 'dart:async'; import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/home.dart'; -import 'package:donationwallet/main.dart'; +import 'package:donationwallet/features/home/home_screen.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -class LoadWalletScreen extends StatelessWidget { - const LoadWalletScreen({super.key}); +class SetupWalletScreen extends StatelessWidget { + const SetupWalletScreen({super.key}); Future _setup(BuildContext context, String? mnemonic, String? scanKey, String? spendKey, int birthday) async { @@ -230,6 +230,10 @@ class LoadWalletScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( + appBar: AppBar( + title: const Text('Wallet creation/restoration'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/features/wallet/controllers/synchronization_service.dart b/lib/features/wallet/controllers/synchronization_service.dart new file mode 100644 index 0000000..87f610b --- /dev/null +++ b/lib/features/wallet/controllers/synchronization_service.dart @@ -0,0 +1,32 @@ +import 'dart:async'; +import 'package:donationwallet/utils/global_functions.dart'; +import 'package:donationwallet/rust/api/simple.dart'; + +class SynchronizationService { + Timer? _timer; + final Duration _interval = const Duration(minutes: 10); + + void startSyncTimer() { + _scheduleNextTask(); + } + + void _scheduleNextTask() async { + _timer?.cancel(); + await performSynchronizationTask(); + _timer = Timer(_interval, () async { + _scheduleNextTask(); + }); + } + + Future performSynchronizationTask() async { + try { + await syncBlockchain(); + } catch (e) { + displayNotification(e.toString()); + } + } + + void stopSyncTimer() { + _timer?.cancel(); + } +} diff --git a/lib/features/wallet/models/wallet_state.dart b/lib/features/wallet/models/wallet_state.dart new file mode 100644 index 0000000..a38a6c3 --- /dev/null +++ b/lib/features/wallet/models/wallet_state.dart @@ -0,0 +1,215 @@ +import 'dart:async'; +import 'package:donationwallet/rust/api/simple.dart'; +import 'package:donationwallet/rust/logger.dart'; +import 'package:donationwallet/features/wallet/controllers/synchronization_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class WalletState extends ChangeNotifier { + final String label = "default"; + BigInt amount = BigInt.from(0); + int birthday = 0; + int lastScan = 0; + int tip = 0; + double progress = 0.0; + bool scanning = false; + String network = 'signet'; + bool walletLoaded = false; + String address = ""; + Map ownedOutputs = {}; + Map selectedOutputs = {}; + List recipients = List.empty(growable: true); + final secureStorage = const FlutterSecureStorage(); + + late StreamSubscription logStreamSubscription; + late StreamSubscription scanProgressSubscription; + late StreamSubscription amountStreamSubscription; + late StreamSubscription syncStreamSubscription; + + final _synchronizationService = SynchronizationService(); + + WalletState(); + + Future initialize() async { + try { + await _initStreams(); + _synchronizationService.startSyncTimer(); + } catch (e) { + rethrow; + } + } + + Future _initStreams() async { + logStreamSubscription = + createLogStream(level: LogLevel.debug, logDependencies: true) + .listen((event) { + print('${event.level} (${event.tag}): ${event.msg}'); + notifyListeners(); + }); + + scanProgressSubscription = createScanProgressStream().listen(((event) { + int start = event.start; + int current = event.current; + int end = event.end; + double scanned = (current - start).toDouble(); + double total = (end - start).toDouble(); + double progress = scanned / total; + if (current == end) { + progress = 0.0; + scanning = false; + } + this.progress = progress; + lastScan = current; + + notifyListeners(); + })); + + syncStreamSubscription = createSyncStream().listen((event) { + tip = event.blockheight; + + print('tip: $tip'); + + notifyListeners(); + }); + + amountStreamSubscription = createAmountStream().listen((event) { + amount = event; + notifyListeners(); + }); + } + + @override + void dispose() { + logStreamSubscription.cancel(); + scanProgressSubscription.cancel(); + amountStreamSubscription.cancel(); + syncStreamSubscription.cancel(); + _synchronizationService.stopSyncTimer(); + super.dispose(); + } + + Future reset() async { + amount = BigInt.zero; + birthday = 0; + lastScan = 0; + progress = 0.0; + scanning = false; + walletLoaded = false; + address = ""; + ownedOutputs = {}; + selectedOutputs = {}; + recipients = List.empty(growable: true); + + notifyListeners(); + } + + Future saveWalletToSecureStorage(String wallet) async { + await secureStorage.write(key: label, value: wallet); + } + + Future rmWalletFromSecureStorage() async { + await secureStorage.write(key: label, value: null); + } + + Future getWalletFromSecureStorage() async { + final wallet = await secureStorage.read(key: label); + if (wallet != null) { + return wallet; + } else { + throw Exception("No wallet in storage"); + } + } + + Future updateWalletStatus() async { + WalletStatus walletInfo; + try { + final encodedWallet = await getWalletFromSecureStorage(); + walletInfo = getWalletInfo(encodedWallet: encodedWallet); + } catch (e) { + rethrow; + } + + address = walletInfo.address; + amount = walletInfo.balance; + birthday = walletInfo.birthday; + lastScan = walletInfo.lastScan; + ownedOutputs = walletInfo.outputs; + notifyListeners(); + } + + Map getSpendableOutputs() { + var spendable = ownedOutputs.entries.where((element) => + element.value.spendStatus == const OutputSpendStatus.unspent()); + return Map.fromEntries(spendable); + } + + void toggleOutputSelection(String outpoint, OwnedOutput output) { + if (selectedOutputs.containsKey(outpoint)) { + selectedOutputs.remove(outpoint); + } else { + selectedOutputs[outpoint] = output; + } + notifyListeners(); + } + + BigInt outputSelectionTotalAmt() { + final total = selectedOutputs.values + .fold(BigInt.zero, (sum, element) => sum + element.amount.field0); + return total; + } + + BigInt recipientTotalAmt() { + final total = recipients.fold( + BigInt.zero, (sum, element) => sum + element.amount.field0); + return total; + } + + Future addRecipients( + String address, BigInt amount, int nbOutputs) async { + final alreadyInList = recipients.where((r) => r.address == address); + if (alreadyInList.isNotEmpty) { + throw Exception("Address already in list"); + } + + if (nbOutputs < 1) { + nbOutputs = 1; + } + + if (amount <= BigInt.from(546)) { + throw Exception("Can't have amount inferior to 546 sats"); + } + recipients.add(Recipient( + address: address, + amount: Amount(field0: amount), + nbOutputs: nbOutputs)); + + notifyListeners(); + } + + Future rmRecipient(String address) async { + final alreadyInList = recipients.where((r) => r.address == address); + if (alreadyInList.isEmpty) { + throw Exception("Unknown recipient"); + } else { + recipients.removeWhere((r) => r.address == address); + } + notifyListeners(); + } + + Future scan() async { + try { + scanning = true; + await syncBlockchain(); + final wallet = await getWalletFromSecureStorage(); + final updatedWallet = await scanToTip(encodedWallet: wallet); + print(updatedWallet); + await saveWalletToSecureStorage(updatedWallet); + } catch (e) { + scanning = false; + notifyListeners(); + rethrow; + } + scanning = false; + notifyListeners(); + } +} diff --git a/lib/destination.dart b/lib/features/wallet/screens/destination_screen.dart similarity index 98% rename from lib/destination.dart rename to lib/features/wallet/screens/destination_screen.dart index 41cd9d5..97e4c29 100644 --- a/lib/destination.dart +++ b/lib/features/wallet/screens/destination_screen.dart @@ -1,4 +1,4 @@ -import 'package:donationwallet/main.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; diff --git a/lib/outputs.dart b/lib/features/wallet/screens/outputs_screen.dart similarity index 97% rename from lib/outputs.dart rename to lib/features/wallet/screens/outputs_screen.dart index 4b311ec..085c3a1 100644 --- a/lib/outputs.dart +++ b/lib/features/wallet/screens/outputs_screen.dart @@ -1,4 +1,4 @@ -import 'package:donationwallet/main.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:donationwallet/rust/api/simple.dart'; diff --git a/lib/spend.dart b/lib/features/wallet/screens/spend_screen.dart similarity index 95% rename from lib/spend.dart rename to lib/features/wallet/screens/spend_screen.dart index 074f570..ed9c97c 100644 --- a/lib/spend.dart +++ b/lib/features/wallet/screens/spend_screen.dart @@ -1,8 +1,9 @@ import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/global_functions.dart'; +import 'package:donationwallet/utils/global_functions.dart'; import 'package:donationwallet/main.dart'; -import 'package:donationwallet/outputs.dart'; -import 'package:donationwallet/destination.dart'; +import 'package:donationwallet/features/wallet/screens/outputs_screen.dart'; +import 'package:donationwallet/features/wallet/screens/destination_screen.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; diff --git a/lib/wallet.dart b/lib/features/wallet/screens/wallet_screen.dart similarity index 96% rename from lib/wallet.dart rename to lib/features/wallet/screens/wallet_screen.dart index aad1c5b..0a7db7a 100644 --- a/lib/wallet.dart +++ b/lib/features/wallet/screens/wallet_screen.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:bitcoin_ui/bitcoin_ui.dart'; -import 'package:donationwallet/global_functions.dart'; -import 'package:donationwallet/main.dart'; -import 'package:donationwallet/spend.dart'; +import 'package:donationwallet/utils/global_functions.dart'; +import 'package:donationwallet/features/wallet/screens/spend_screen.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:barcode_widget/barcode_widget.dart'; diff --git a/lib/home.dart b/lib/home.dart deleted file mode 100644 index 65471e5..0000000 --- a/lib/home.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:async'; - -import 'package:bitcoin_ui/bitcoin_ui.dart'; -import 'package:donationwallet/load_wallet.dart'; -import 'package:donationwallet/main.dart'; -import 'package:donationwallet/wallet.dart'; -import 'package:donationwallet/settings.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); - - @override - HomeScreenState createState() => HomeScreenState(); -} - -class HomeScreenState extends State { - bool isLoading = true; - int _selectedIndex = 0; - final List _widgetOptions = [ - const WalletScreen(), - const SettingsScreen(), - ]; - - @override - void initState() { - super.initState(); - _checkWallet(); - } - - Future _checkWallet() async { - final walletState = Provider.of(context, listen: false); - try { - await walletState.updateWalletStatus(); - walletState.walletLoaded = true; - } catch (e) { - walletState.walletLoaded = false; - } - setState(() { - isLoading = false; - }); - } - - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } - - @override - Widget build(BuildContext context) { - if (isLoading) { - return const MaterialApp( - home: Scaffold( - body: Center(child: CircularProgressIndicator()), - ), - ); - } - - final walletState = Provider.of(context); - if (!walletState.walletLoaded) { - return Scaffold( - appBar: AppBar( - title: const Text('Wallet creation/restoration'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: const LoadWalletScreen(), - ); - } else { - return Scaffold( - appBar: AppBar( - title: const Text('Silent Payments'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: IndexedStack( - index: _selectedIndex, - children: _widgetOptions, - ), - bottomNavigationBar: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: Image( - image: const AssetImage("icons/wallet.png", - package: "bitcoin_ui"), - color: Bitcoin.neutral3Dark), - label: 'Wallet', - ), - BottomNavigationBarItem( - icon: Image( - image: - const AssetImage("icons/gear.png", package: "bitcoin_ui"), - color: Bitcoin.neutral3Dark), - label: 'Settings', - ), - ], - currentIndex: _selectedIndex, - selectedItemColor: Colors.green, - onTap: _onItemTapped, - ), - ); - } - } -} diff --git a/lib/main.dart b/lib/main.dart index 3ef1fcf..1496f38 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,267 +1,44 @@ -import 'dart:async'; import 'package:bitcoin_ui/bitcoin_ui.dart'; -import 'package:donationwallet/rust/api/simple.dart'; import 'package:donationwallet/rust/frb_generated.dart'; -import 'package:donationwallet/rust/logger.dart'; -import 'package:donationwallet/global_functions.dart'; -import 'package:donationwallet/home.dart'; +import 'package:donationwallet/utils/global_functions.dart'; +import 'package:donationwallet/features/home/home_screen.dart'; +import 'package:donationwallet/features/setup/screens/setup_wallet_screen.dart'; +import 'package:donationwallet/features/wallet/models/wallet_state.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:provider/provider.dart'; -class SynchronizationService { - Timer? _timer; - final Duration _interval = const Duration(minutes: 10); - - void startSyncTimer() { - _scheduleNextTask(); - } - - void _scheduleNextTask() async { - _timer?.cancel(); - await performSynchronizationTask(); - _timer = Timer(_interval, () async { - _scheduleNextTask(); - }); - } - - Future performSynchronizationTask() async { - try { - await syncBlockchain(); - } catch (e) { - displayNotification(e.toString()); - } - } - - void stopSyncTimer() { - _timer?.cancel(); - } -} - -class WalletState extends ChangeNotifier { - final String label = "default"; - BigInt amount = BigInt.from(0); - int birthday = 0; - int lastScan = 0; - int tip = 0; - double progress = 0.0; - bool scanning = false; - String network = 'signet'; - bool walletLoaded = false; - String address = ""; - Map ownedOutputs = {}; - Map selectedOutputs = {}; - List recipients = List.empty(growable: true); - final secureStorage = const FlutterSecureStorage(); - - late StreamSubscription logStreamSubscription; - late StreamSubscription scanProgressSubscription; - late StreamSubscription amountStreamSubscription; - late StreamSubscription syncStreamSubscription; - - final _synchronizationService = SynchronizationService(); - - WalletState(); - - Future initialize() async { - try { - await _initStreams(); - _synchronizationService.startSyncTimer(); - } catch (e) { - rethrow; - } - } - - Future _initStreams() async { - logStreamSubscription = - createLogStream(level: LogLevel.debug, logDependencies: true) - .listen((event) { - print('${event.level} (${event.tag}): ${event.msg}'); - notifyListeners(); - }); - - scanProgressSubscription = createScanProgressStream().listen(((event) { - int start = event.start; - int current = event.current; - int end = event.end; - double scanned = (current - start).toDouble(); - double total = (end - start).toDouble(); - double progress = scanned / total; - if (current == end) { - progress = 0.0; - scanning = false; - } - this.progress = progress; - lastScan = current; - - notifyListeners(); - })); - - syncStreamSubscription = createSyncStream().listen((event) { - tip = event.blockheight; - - print('tip: $tip'); - - notifyListeners(); - }); - - amountStreamSubscription = createAmountStream().listen((event) { - amount = event; - notifyListeners(); - }); - } - - @override - void dispose() { - logStreamSubscription.cancel(); - scanProgressSubscription.cancel(); - amountStreamSubscription.cancel(); - syncStreamSubscription.cancel(); - _synchronizationService.stopSyncTimer(); - super.dispose(); - } - - Future reset() async { - amount = BigInt.zero; - birthday = 0; - lastScan = 0; - progress = 0.0; - scanning = false; - walletLoaded = false; - address = ""; - ownedOutputs = {}; - selectedOutputs = {}; - recipients = List.empty(growable: true); - - notifyListeners(); - } - - Future saveWalletToSecureStorage(String wallet) async { - await secureStorage.write(key: label, value: wallet); - } - - Future rmWalletFromSecureStorage() async { - await secureStorage.write(key: label, value: null); - } - - Future getWalletFromSecureStorage() async { - final wallet = await secureStorage.read(key: label); - if (wallet != null) { - return wallet; - } else { - throw Exception("No wallet in storage"); - } - } - - Future updateWalletStatus() async { - WalletStatus walletInfo; - try { - final wallet = await getWalletFromSecureStorage(); - walletInfo = getWalletInfo(encodedWallet: wallet); - } catch (e) { - rethrow; - } - address = walletInfo.address; - amount = walletInfo.balance; - birthday = walletInfo.birthday; - lastScan = walletInfo.lastScan; - ownedOutputs = walletInfo.outputs; - notifyListeners(); - } - - Map getSpendableOutputs() { - var spendable = ownedOutputs.entries.where((element) => - element.value.spendStatus == const OutputSpendStatus.unspent()); - return Map.fromEntries(spendable); - } - - void toggleOutputSelection(String outpoint, OwnedOutput output) { - if (selectedOutputs.containsKey(outpoint)) { - selectedOutputs.remove(outpoint); - } else { - selectedOutputs[outpoint] = output; - } - notifyListeners(); - } - - BigInt outputSelectionTotalAmt() { - final total = selectedOutputs.values - .fold(BigInt.zero, (sum, element) => sum + element.amount.field0); - return total; - } - - BigInt recipientTotalAmt() { - final total = recipients.fold( - BigInt.zero, (sum, element) => sum + element.amount.field0); - return total; - } - - Future addRecipients( - String address, BigInt amount, int nbOutputs) async { - final alreadyInList = recipients.where((r) => r.address == address); - if (alreadyInList.isNotEmpty) { - throw Exception("Address already in list"); - } - - if (nbOutputs < 1) { - nbOutputs = 1; - } - - if (amount <= BigInt.from(546)) { - throw Exception("Can't have amount inferior to 546 sats"); - } - recipients.add(Recipient( - address: address, - amount: Amount(field0: amount), - nbOutputs: nbOutputs)); - - notifyListeners(); - } - - Future rmRecipient(String address) async { - final alreadyInList = recipients.where((r) => r.address == address); - if (alreadyInList.isEmpty) { - throw Exception("Unknown recipient"); - } else { - recipients.removeWhere((r) => r.address == address); - } - notifyListeners(); - } - - Future scan() async { - try { - scanning = true; - await syncBlockchain(); - final wallet = await getWalletFromSecureStorage(); - final updatedWallet = await scanToTip(encodedWallet: wallet); - print(updatedWallet); - await saveWalletToSecureStorage(updatedWallet); - } catch (e) { - scanning = false; - notifyListeners(); - rethrow; - } - scanning = false; - notifyListeners(); - } -} - void main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); final walletState = WalletState(); await walletState.initialize(); + + bool walletInitialized; + + try { + await walletState.updateWalletStatus(); + walletState.walletLoaded = true; + walletInitialized = true; + } catch (e) { + walletState.walletLoaded = false; + walletInitialized = false; + } + runApp( ChangeNotifierProvider.value( value: walletState, - child: const SilentPaymentApp(), + child: SilentPaymentApp( + walletInitialized: walletInitialized, + ), ), ); } class SilentPaymentApp extends StatelessWidget { - const SilentPaymentApp({super.key}); + final bool walletInitialized; + + const SilentPaymentApp({super.key, required this.walletInitialized}); @override Widget build(BuildContext context) { @@ -272,7 +49,7 @@ class SilentPaymentApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Bitcoin.green), useMaterial3: true, ), - home: const HomeScreen(), + home: walletInitialized ? const HomeScreen() : const SetupWalletScreen(), ); } } diff --git a/lib/global_functions.dart b/lib/utils/global_functions.dart similarity index 100% rename from lib/global_functions.dart rename to lib/utils/global_functions.dart