diff --git a/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart b/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart index ab85dd776..b2f2dcbe3 100644 --- a/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart +++ b/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart @@ -38,7 +38,7 @@ class _FrostScaffoldState extends ConsumerState { if (context.mounted) { Navigator.of(context).pop(); - ref.read(pFrostCreateNewArgs.state).state = null; + ref.read(pFrostScaffoldArgs.state).state = null; } _requestPopLock = false; @@ -46,7 +46,7 @@ class _FrostScaffoldState extends ConsumerState { @override void initState() { - _routes = ref.read(pFrostCreateNewArgs)!.$2; + _routes = ref.read(pFrostScaffoldArgs)!.stepRoutes; super.initState(); } diff --git a/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart b/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart index b0ddfb3e8..f9f3b8caf 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart @@ -408,13 +408,14 @@ class _NewFrostMsWalletViewState controllers.first.text.trim(); ref.read(pFrostMultisigConfig.notifier).state = config; - ref.read(pFrostCreateNewArgs.state).state = ( - ( + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( walletName: widget.walletName, frostCurrency: widget.frostCurrency, ), - FrostRouteGenerator.createNewConfigStepRoutes, - () { + walletId: null, + stepRoutes: FrostRouteGenerator.createNewConfigStepRoutes, + onSuccess: () { // successful completion of steps if (Util.isDesktop) { Navigator.of(context).popUntil( @@ -434,7 +435,7 @@ class _NewFrostMsWalletViewState ref.read(pFrostMultisigConfig.state).state = null; ref.read(pFrostStartKeyGenData.state).state = null; ref.read(pFrostSecretSharesData.state).state = null; - ref.read(pFrostCreateNewArgs.state).state = null; + ref.read(pFrostScaffoldArgs.state).state = null; unawaited( showFloatingFlushBar( diff --git a/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart b/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart index 7a797ed9a..ddf01aacb 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart @@ -7,7 +7,6 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -141,13 +140,14 @@ class _SelectNewFrostImportTypeViewState final String route; switch (_selectedOption) { case _ImportOption.multisigNew: - ref.read(pFrostCreateNewArgs.state).state = ( - ( + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( walletName: widget.walletName, frostCurrency: widget.frostCurrency, ), - FrostRouteGenerator.importNewConfigStepRoutes, - () { + walletId: null, // no wallet id yet + stepRoutes: FrostRouteGenerator.importNewConfigStepRoutes, + onSuccess: () { // successful completion of steps if (Util.isDesktop) { Navigator.of(context).popUntil( @@ -167,7 +167,7 @@ class _SelectNewFrostImportTypeViewState ref.read(pFrostMultisigConfig.state).state = null; ref.read(pFrostStartKeyGenData.state).state = null; ref.read(pFrostSecretSharesData.state).state = null; - ref.read(pFrostCreateNewArgs.state).state = null; + ref.read(pFrostScaffoldArgs.state).state = null; unawaited( showFloatingFlushBar( @@ -179,22 +179,26 @@ class _SelectNewFrostImportTypeViewState ); } ); - - await Navigator.of(context).pushNamed( - FrostStepScaffold.routeName, - ); break; case _ImportOption.resharerExisting: - await Navigator.of(context).pushNamed( - NewImportResharerConfigView.routeName, - arguments: ( + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( walletName: widget.walletName, frostCurrency: widget.frostCurrency, ), + walletId: null, // no wallet id yet + stepRoutes: FrostRouteGenerator.joinReshareStepRoutes, + onSuccess: () { + // successful completion of steps + } ); break; } + + await Navigator.of(context).pushNamed( + FrostStepScaffold.routeName, + ); }, ) ], diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart index f4fab1000..8c292c574 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -260,7 +259,10 @@ class _FrostCreateStep1aState extends ConsumerState { ref.read(pFrostCreateCurrentStep.state).state = 2; await Navigator.of(context).pushNamed( - FrostCreateStep2.routeName, + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, // FrostShareCommitmentsView.routeName, ); }, diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart index 88d3af415..ff9c01729 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart @@ -4,7 +4,6 @@ import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/services/frost.dart'; @@ -356,8 +355,11 @@ class _FrostCreateStep1bState extends ConsumerState { myName: ref.read(pFrostMyName.state).state!, ); ref.read(pFrostCreateCurrentStep.state).state = 2; - Navigator.of(context).pushNamed( - FrostCreateStep2.routeName, + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, ); }, ) diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart index 7f755cfa2..dea6b9dcb 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -343,7 +342,10 @@ class _FrostCreateStep2State extends ConsumerState { ref.read(pFrostCreateCurrentStep.state).state = 3; await Navigator.of(context).pushNamed( - FrostCreateStep3.routeName, + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, ); } catch (e, s) { Logging.instance.log( diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart index 55da4c407..1d6de5c39 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -293,7 +292,10 @@ class _FrostCreateStep3State extends ConsumerState { ref.read(pFrostCreateCurrentStep.state).state = 4; await Navigator.of(context).pushNamed( - FrostCreateStep4.routeName, + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, ); } catch (e, s) { Logging.instance.log( diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart index 586c45dc0..93fff4f77 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart @@ -2,7 +2,6 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -62,9 +61,12 @@ class _FrostCreateStep4State extends ConsumerState { PrimaryButton( label: "Confirm", onPressed: () { - ref.read(pFrostCreateCurrentStep.state).state = 4; + ref.read(pFrostCreateCurrentStep.state).state = 5; Navigator.of(context).pushNamed( - FrostCreateStep5.routeName, + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, ); }, ) diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart index 7de9c4828..6b7a674c0 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart @@ -162,11 +162,11 @@ class _FrostCreateStep5State extends ConsumerState { ), ); - final data = ref.read(pFrostCreateNewArgs)!; + final data = ref.read(pFrostScaffoldArgs)!; final info = WalletInfo.createNew( - coin: data.$1.frostCurrency.coin, - name: data.$1.walletName, + coin: data.info.frostCurrency.coin, + name: data.info.walletName, ); final wallet = await Wallet.create( @@ -206,7 +206,7 @@ class _FrostCreateStep5State extends ConsumerState { } if (mounted) { - ref.read(pFrostCreateNewArgs)!.$3(); + ref.read(pFrostScaffoldArgs)!.onSuccess(); } } catch (e, s) { Logging.instance.log( diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart index 5e848fd48..67e96136b 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart +++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart @@ -7,18 +7,28 @@ import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_crea import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart'; typedef FrostStepRoute = ({String routeName, String title}); final pFrostCreateCurrentStep = StateProvider.autoDispose((ref) => 1); -final pFrostCreateNewArgs = StateProvider< - ( - ({String walletName, FrostCurrency frostCurrency}), - List, - VoidCallback, - )?>((ref) => null); +final pFrostScaffoldArgs = StateProvider< + ({ + ({String walletName, FrostCurrency frostCurrency}) info, + String? walletId, + List stepRoutes, + VoidCallback onSuccess, + })?>((ref) => null); abstract class FrostRouteGenerator { static const bool useMaterialPageRoute = true; @@ -39,6 +49,42 @@ abstract class FrostRouteGenerator { (routeName: FrostCreateStep5.routeName, title: FrostCreateStep5.title), ]; + static const List initiateReshareStepRoutes = [ + (routeName: FrostReshareStep1a.routeName, title: FrostReshareStep1a.title), + ( + routeName: FrostReshareStep2abd.routeName, + title: FrostReshareStep2abd.title + ), + ( + routeName: FrostReshareStep3abd.routeName, + title: FrostReshareStep3abd.title + ), + (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title), + (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title), + ]; + + static const List importReshareStepRoutes = [ + (routeName: FrostReshareStep1b.routeName, title: FrostReshareStep1b.title), + ( + routeName: FrostReshareStep2abd.routeName, + title: FrostReshareStep2abd.title + ), + ( + routeName: FrostReshareStep3abd.routeName, + title: FrostReshareStep3abd.title + ), + (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title), + (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title), + ]; + + static const List joinReshareStepRoutes = [ + (routeName: FrostReshareStep1c.routeName, title: FrostReshareStep1c.title), + (routeName: FrostReshareStep2c.routeName, title: FrostReshareStep2c.title), + (routeName: FrostReshareStep3c.routeName, title: FrostReshareStep3c.title), + (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title), + (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title), + ]; + static Route generateRoute(RouteSettings settings) { final args = settings.arguments; @@ -85,6 +131,69 @@ abstract class FrostRouteGenerator { settings: settings, ); + case FrostReshareStep1a.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep1a(), + settings: settings, + ); + + case FrostReshareStep1b.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep1b(), + settings: settings, + ); + + case FrostReshareStep1c.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep1c(), + settings: settings, + ); + + case FrostReshareStep2abd.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep2abd(), + settings: settings, + ); + + case FrostReshareStep2c.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep2c(), + settings: settings, + ); + + case FrostReshareStep3abd.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep2abd(), + settings: settings, + ); + + case FrostReshareStep3c.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep2c(), + settings: settings, + ); + + case FrostReshareStep4.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep4(), + settings: settings, + ); + + case FrostReshareStep5.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostReshareStep5(), + settings: settings, + ); + default: return _routeError(""); } diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart similarity index 55% rename from lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart rename to lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart index fa538f29e..d99851e03 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -15,12 +15,7 @@ import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/detail_item.dart'; @@ -28,23 +23,17 @@ import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart'; import 'package:stackwallet/widgets/frost_step_user_steps.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -class DisplayReshareConfigView extends ConsumerStatefulWidget { - const DisplayReshareConfigView({ - super.key, - required this.walletId, - }); +class FrostReshareStep1a extends ConsumerStatefulWidget { + const FrostReshareStep1a({super.key}); - static const String routeName = "/displayReshareConfigView"; - - final String walletId; + static const String routeName = "/frostReshareStep1a"; + static const String title = "Resharer config"; @override - ConsumerState createState() => - _DisplayReshareConfigViewState(); + ConsumerState createState() => _FrostReshareStep1aState(); } -class _DisplayReshareConfigViewState - extends ConsumerState { +class _FrostReshareStep1aState extends ConsumerState { static const info = [ "Share this config with the signing group participants as well as any new " "participant.", @@ -67,7 +56,8 @@ class _DisplayReshareConfigViewState try { final wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; + ref.read(pWallets).getWallet(ref.read(pFrostScaffoldArgs)!.walletId!) + as BitcoinFrostWallet; final serializedKeys = await wallet.getSerializedKeys(); if (mounted) { @@ -78,9 +68,12 @@ class _DisplayReshareConfigViewState ref.read(pFrostResharingData).startResharerData = result; + ref.read(pFrostCreateCurrentStep.state).state = 2; await Navigator.of(context).pushNamed( - BeginResharingView.routeName, - arguments: widget.walletId, + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, ); } } catch (e, s) { @@ -215,7 +208,7 @@ class _DisplayReshareConfigViewState .read(mainDBProvider) .isar .frostWalletInfo - .getByWalletIdSync(widget.walletId)!; + .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!; final myOldIndex = frostInfo.participants.indexOf(frostInfo.myName); @@ -229,164 +222,110 @@ class _DisplayReshareConfigViewState @override Widget build(BuildContext context) { - return ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Resharer config", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const FrostStepUserSteps( + userSteps: info, ), - ), - child: Column( - children: [ - const SizedBox( - height: 16, - ), - const FrostStepUserSteps( - userSteps: info, - ), - const SizedBox(height: 20), - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: ref.watch(pFrostResharingData).resharerConfig!, - size: 220, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const SizedBox( - height: 32, - ), - DetailItem( - title: "Config", - detail: ref.watch(pFrostResharingData).resharerConfig!, - button: Util.isDesktop - ? IconCopyButton( - data: ref.watch(pFrostResharingData).resharerConfig!, - ) - : SimpleCopyButton( - data: ref.watch(pFrostResharingData).resharerConfig!, - ), - ), - SizedBox( - height: Util.isDesktop ? 64 : 16, - ), - Row( + const SizedBox(height: 20), + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Expanded( - child: SecondaryButton( - label: "Show group participants", - onPressed: _showParticipantsDialog, - ), + QrImageView( + data: ref.watch(pFrostResharingData).resharerConfig!, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, ), ], ), - if (iAmInvolved && !Util.isDesktop) const Spacer(), - if (iAmInvolved) - const SizedBox( - height: 16, - ), - if (iAmInvolved) - GestureDetector( - onTap: () { - setState(() { - _userVerifyContinue = !_userVerifyContinue; - }); - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 26, - child: Checkbox( - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - value: _userVerifyContinue, - onChanged: (value) => setState( - () => _userVerifyContinue = value == true, - ), - ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Text( - "I have verified that everyone has imported the config", - style: STextStyles.w500_14(context), - ), - ), - ], + ), + const SizedBox( + height: 32, + ), + DetailItem( + title: "Config", + detail: ref.watch(pFrostResharingData).resharerConfig!, + button: Util.isDesktop + ? IconCopyButton( + data: ref.watch(pFrostResharingData).resharerConfig!, + ) + : SimpleCopyButton( + data: ref.watch(pFrostResharingData).resharerConfig!, ), + ), + SizedBox( + height: Util.isDesktop ? 64 : 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Show group participants", + onPressed: _showParticipantsDialog, ), ), - if (iAmInvolved) - const SizedBox( - height: 16, + ], + ), + if (iAmInvolved && !Util.isDesktop) const Spacer(), + if (iAmInvolved) + const SizedBox( + height: 16, + ), + if (iAmInvolved) + GestureDetector( + onTap: () { + setState(() { + _userVerifyContinue = !_userVerifyContinue; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 26, + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _userVerifyContinue, + onChanged: (value) => setState( + () => _userVerifyContinue = value == true, + ), + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + "I have verified that everyone has imported the config", + style: STextStyles.w500_14(context), + ), + ), + ], + ), ), - if (iAmInvolved) - PrimaryButton( - label: "Start resharing", - enabled: _userVerifyContinue, - onPressed: _onPressed, - ), - ], - ), + ), + if (iAmInvolved) + const SizedBox( + height: 16, + ), + if (iAmInvolved) + PrimaryButton( + label: "Start resharing", + enabled: _userVerifyContinue, + onPressed: _onPressed, + ), + ], ), ); } diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart new file mode 100644 index 000000000..b9c41ec7d --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart @@ -0,0 +1,349 @@ +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:frostdart/frostdart.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/services/frost.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/frost_step_user_steps.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FrostReshareStep1b extends ConsumerStatefulWidget { + const FrostReshareStep1b({ + super.key, + }); + + static const String routeName = "/frostReshareStep1b"; + static const String title = "Import reshare config"; + + @override + ConsumerState createState() => _FrostReshareStep1bState(); +} + +class _FrostReshareStep1bState extends ConsumerState { + static const info = [ + "Scan the config QR code or paste the code provided by the group member who" + " is initiating resharing.", + "Wait for other participants to finish importing the config.", + "Verify that everyone has filled out their forms before continuing. If you " + "try to continue before everyone is ready, the process will be canceled.", + "Check the box and press “Start resharing”.", + ]; + + late final TextEditingController configFieldController; + late final FocusNode configFocusNode; + + bool _configEmpty = true; + + bool _buttonLock = false; + bool _userVerifyContinue = false; + + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + final walletId = ref.read(pFrostScaffoldArgs)!.walletId!; + // TODO: optimize this by creating watcher providers (similar to normal WalletInfo) + final frostInfo = ref + .read(mainDBProvider) + .isar + .frostWalletInfo + .getByWalletIdSync(walletId)!; + + ref.read(pFrostResharingData).reset(); + ref.read(pFrostResharingData).myName = frostInfo.myName; + ref.read(pFrostResharingData).resharerConfig = configFieldController.text; + + String? salt; + try { + salt = Format.uint8listToString( + resharerSalt( + resharerConfig: ref.read(pFrostResharingData).resharerConfig!, + ), + ); + } catch (_) { + throw Exception("Bad resharer config"); + } + + if (frostInfo.knownSalts.contains(salt)) { + throw Exception("Duplicate config salt"); + } else { + final salts = frostInfo.knownSalts.toList(); + salts.add(salt); + final mainDB = ref.read(mainDBProvider); + await mainDB.isar.writeTxn(() async { + final id = frostInfo.id; + await mainDB.isar.frostWalletInfo.delete(id); + await mainDB.isar.frostWalletInfo.put( + frostInfo.copyWith(knownSalts: salts), + ); + }); + } + + final serializedKeys = await ref.read(secureStoreProvider).read( + key: "{$walletId}_serializedFROSTKeys", + ); + if (mounted) { + final result = Frost.beginResharer( + serializedKeys: serializedKeys!, + config: ref.read(pFrostResharingData).resharerConfig!, + ); + + ref.read(pFrostResharingData).startResharerData = result; + + ref.read(pFrostCreateCurrentStep.state).state = 2; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + } finally { + _buttonLock = false; + } + } + + @override + void initState() { + configFieldController = TextEditingController(); + configFocusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + configFieldController.dispose(); + configFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 16, + ), + const FrostStepUserSteps( + userSteps: info, + ), + const SizedBox(height: 20), + Text( + "Enter config", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("frConfigTextFieldKey"), + controller: configFieldController, + onChanged: (_) { + setState(() { + _configEmpty = configFieldController.text.isEmpty; + }); + }, + focusNode: configFocusNode, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter config", + configFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _configEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + !_configEmpty + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Config Field.", + key: const Key("frConfigClearButtonKey"), + onTap: () { + configFieldController.text = ""; + + setState(() { + _configEmpty = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Config Field Input.", + key: const Key("frConfigPasteButtonKey"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + configFieldController.text = + data.text!.trim(); + } + + setState(() { + _configEmpty = + configFieldController.text.isEmpty; + }); + }, + child: _configEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_configEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key("frConfigScanQrButtonKey"), + onTap: () async { + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + + final qrResult = await BarcodeScanner.scan(); + + configFieldController.text = + qrResult.rawContent; + + setState(() { + _configEmpty = + configFieldController.text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ) + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + GestureDetector( + onTap: () { + setState(() { + _userVerifyContinue = !_userVerifyContinue; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 26, + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _userVerifyContinue, + onChanged: (value) => setState( + () => _userVerifyContinue = value == true, + ), + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + "I have verified that everyone has imported the config", + style: STextStyles.w500_14(context), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: "Start resharing", + enabled: !_configEmpty && _userVerifyContinue, + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + } + + await _onPressed(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart new file mode 100644 index 000000000..3e0e17fdf --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart @@ -0,0 +1,425 @@ +import 'dart:async'; + +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; +import 'package:stackwallet/wallets/models/incomplete_frost_wallet.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/frost_step_user_steps.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FrostReshareStep1c extends ConsumerStatefulWidget { + const FrostReshareStep1c({super.key}); + + static const String routeName = "/frostReshareStep1c"; + static const String title = "Import reshare config"; + + @override + ConsumerState createState() => _FrostReshareStep1cState(); +} + +class _FrostReshareStep1cState extends ConsumerState { + static const info = [ + "Scan the config QR code or paste the code provided by the group creator.", + "Enter your name EXACTLY as the group creator entered it. When in doubt, " + "double check with them. The names are case-sensitive.", + "Wait for other participants to finish entering their information.", + "Verify that everyone has filled out their forms before continuing. If you " + "try to continue before everyone is ready, the process could be canceled.", + "Check the box and press “Join group”.", + ]; + + late final TextEditingController myNameFieldController, configFieldController; + late final FocusNode myNameFocusNode, configFocusNode; + + bool _nameEmpty = true, + _configEmpty = true, + _userVerifyContinue = false, + _buttonLock = false; + + Future _createWallet() async { + final data = ref.read(pFrostScaffoldArgs)!; + + final info = WalletInfo.createNew( + name: data.info.walletName, + coin: data.info.frostCurrency.coin, + ); + + final wallet = IncompleteFrostWallet(); + wallet.info = info; + + return wallet; + } + + @override + void initState() { + myNameFieldController = TextEditingController(); + configFieldController = TextEditingController(); + myNameFocusNode = FocusNode(); + configFocusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + myNameFieldController.dispose(); + configFieldController.dispose(); + myNameFocusNode.dispose(); + configFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const FrostStepUserSteps( + userSteps: info, + ), + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("frMyNameTextFieldKey"), + controller: myNameFieldController, + onChanged: (_) { + setState(() { + _nameEmpty = myNameFieldController.text.isEmpty; + }); + }, + focusNode: myNameFocusNode, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "My name", + myNameFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _nameEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + !_nameEmpty + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Config Field.", + key: const Key("frMyNameClearButtonKey"), + onTap: () { + myNameFieldController.text = ""; + + setState(() { + _nameEmpty = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Name Field.", + key: const Key("frMyNamePasteButtonKey"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + myNameFieldController.text = + data.text!.trim(); + } + + setState(() { + _nameEmpty = + myNameFieldController.text.isEmpty; + }); + }, + child: _nameEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("frConfigTextFieldKey"), + controller: configFieldController, + onChanged: (_) { + setState(() { + _configEmpty = configFieldController.text.isEmpty; + }); + }, + focusNode: configFocusNode, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter config", + configFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _configEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + !_configEmpty + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Config Field.", + key: const Key("frConfigClearButtonKey"), + onTap: () { + configFieldController.text = ""; + + setState(() { + _configEmpty = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Config Field Input.", + key: const Key("frConfigPasteButtonKey"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + configFieldController.text = + data.text!.trim(); + } + + setState(() { + _configEmpty = + configFieldController.text.isEmpty; + }); + }, + child: _configEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_configEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key("frConfigScanQrButtonKey"), + onTap: () async { + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + + final qrResult = await BarcodeScanner.scan(); + + configFieldController.text = + qrResult.rawContent; + + setState(() { + _configEmpty = + configFieldController.text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ) + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + GestureDetector( + onTap: () { + setState(() { + _userVerifyContinue = !_userVerifyContinue; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 26, + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _userVerifyContinue, + onChanged: (value) => setState( + () => _userVerifyContinue = value == true, + ), + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + "I have verified that everyone has joined the group", + style: STextStyles.w500_14(context), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: "Join group", + enabled: _userVerifyContinue && !_nameEmpty && !_configEmpty, + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + } + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + ref.read(pFrostResharingData).reset(); + ref.read(pFrostResharingData).myName = + myNameFieldController.text; + ref.read(pFrostResharingData).resharerConfig = + configFieldController.text; + + if (!ref + .read(pFrostResharingData) + .configData! + .newParticipants + .contains(ref.read(pFrostResharingData).myName!)) { + ref.read(pFrostResharingData).reset(); + return await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "My name not found in config participants", + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + + Exception? ex; + final wallet = await showLoading( + whileFuture: _createWallet(), + context: context, + message: "Setting up wallet", + isDesktop: Util.isDesktop, + onException: (e) => ex = e, + ); + + if (ex != null) { + throw ex!; + } + + if (context.mounted) { + ref.read(pFrostResharingData).incompleteWallet = wallet!; + final data = ref.read(pFrostScaffoldArgs)!; + ref.read(pFrostScaffoldArgs.state).state = ( + info: data.info, + walletId: wallet.walletId, + stepRoutes: data.stepRoutes, + onSuccess: data.onSuccess, + ); + ref.read(pFrostCreateCurrentStep.state).state = 2; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + if (context.mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + } finally { + _buttonLock = false; + } + }, + ) + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart new file mode 100644 index 000000000..fdbb2f4cf --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart @@ -0,0 +1,343 @@ +import 'dart:async'; + +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/services/frost.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/detail_item.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FrostReshareStep2abd extends ConsumerStatefulWidget { + const FrostReshareStep2abd({super.key}); + + static const String routeName = "/FrostReshareStep2abd"; + static const String title = "Resharers"; + + @override + ConsumerState createState() => + _FrostReshareStep2abdState(); +} + +class _FrostReshareStep2abdState extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final List resharerIndexes; + late final int myResharerIndexIndex; + late final String myResharerStart; + late final bool amOutgoingParticipant; + + final List fieldIsEmptyFlags = []; + + bool _buttonLock = false; + + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + if (!amOutgoingParticipant) { + // collect resharer strings + final resharerStarts = controllers.map((e) => e.text).toList(); + if (myResharerIndexIndex >= 0) { + // only insert my own at the correct index if I am a resharer + resharerStarts.insert(myResharerIndexIndex, myResharerStart); + } + + final result = Frost.beginReshared( + myName: ref.read(pFrostResharingData).myName!, + resharerConfig: ref.read(pFrostResharingData).resharerConfig!, + resharerStarts: resharerStarts, + ); + + ref.read(pFrostResharingData).startResharedData = result; + } + + ref.read(pFrostCreateCurrentStep.state).state = 3; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + } finally { + _buttonLock = false; + } + } + + @override + void initState() { + // TODO: optimize this by creating watcher providers (similar to normal WalletInfo) + final frostInfo = ref + .read(mainDBProvider) + .isar + .frostWalletInfo + .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!; + final myOldIndex = + frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!); + + myResharerStart = + ref.read(pFrostResharingData).startResharerData!.resharerStart; + + resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; + myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex); + if (myResharerIndexIndex >= 0) { + // remove my name for now as we don't need a text field for it + resharerIndexes.removeAt(myResharerIndexIndex); + } + + amOutgoingParticipant = !ref + .read(pFrostResharingData) + .configData! + .newParticipants + .contains(ref.read(pFrostResharingData).myName!); + + for (int i = 0; i < resharerIndexes.length; i++) { + controllers.add(TextEditingController()); + focusNodes.add(FocusNode()); + fieldIsEmptyFlags.add(true); + } + super.initState(); + } + + @override + void dispose() { + for (int i = 0; i < controllers.length; i++) { + controllers[i].dispose(); + } + for (int i = 0; i < focusNodes.length; i++) { + focusNodes[i].dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: myResharerStart, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "My resharer", + detail: myResharerStart, + button: Util.isDesktop + ? IconCopyButton( + data: myResharerStart, + ) + : SimpleCopyButton( + data: myResharerStart, + ), + ), + const SizedBox( + height: 12, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < resharerIndexes.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: Key("frostResharerTextFieldKey_$i"), + controller: controllers[i], + focusNode: focusNodes[i], + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + onChanged: (_) { + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter index " + "${resharerIndexes[i]}" + "'s resharer", + focusNodes[i], + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: fieldIsEmptyFlags[i] + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + !fieldIsEmptyFlags[i] + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Resharer Field Input.", + key: Key( + "frostResharerClearButtonKey_$i"), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Resharer Field Input.", + key: Key( + "frostResharerPasteButtonKey_$i"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + controllers[i].text = + data.text!.trim(); + } + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + child: fieldIsEmptyFlags[i] + ? const ClipboardIcon() + : const XIcon(), + ), + if (fieldIsEmptyFlags[i]) + TextFieldIconButton( + semanticsLabel: "Scan QR Button. " + "Opens Camera For Scanning QR Code.", + key: Key( + "frostCommitmentsScanQrButtonKey_$i"), + onTap: () async { + try { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await BarcodeScanner.scan(); + + controllers[i].text = + qrResult.rawContent; + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions " + "while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Continue", + enabled: amOutgoingParticipant || + !fieldIsEmptyFlags.reduce((v, e) => v |= e), + onPressed: _onPressed, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart new file mode 100644 index 000000000..88c112095 --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart @@ -0,0 +1,265 @@ +import 'dart:async'; + +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/services/frost.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FrostReshareStep2c extends ConsumerStatefulWidget { + const FrostReshareStep2c({super.key}); + + static const String routeName = "/FrostReshareStep2c"; + static const String title = "Resharers"; + + @override + ConsumerState createState() => _FrostReshareStep2cState(); +} + +class _FrostReshareStep2cState extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final List resharerIndexes; + + final List fieldIsEmptyFlags = []; + + bool _buttonLock = false; + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + // collect resharer strings + final resharerStarts = controllers.map((e) => e.text).toList(); + + final result = Frost.beginReshared( + myName: ref.read(pFrostResharingData).myName!, + resharerConfig: ref.read(pFrostResharingData).resharerConfig!, + resharerStarts: resharerStarts, + ); + + ref.read(pFrostResharingData).startResharedData = result; + + ref.read(pFrostCreateCurrentStep.state).state = 3; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } finally { + _buttonLock = false; + } + } + + @override + void initState() { + resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; + + for (int i = 0; i < resharerIndexes.length; i++) { + controllers.add(TextEditingController()); + focusNodes.add(FocusNode()); + fieldIsEmptyFlags.add(true); + } + super.initState(); + } + + @override + void dispose() { + for (int i = 0; i < controllers.length; i++) { + controllers[i].dispose(); + } + for (int i = 0; i < focusNodes.length; i++) { + focusNodes[i].dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < resharerIndexes.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: Key("frostResharerTextFieldKey_$i"), + controller: controllers[i], + focusNode: focusNodes[i], + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + onChanged: (_) { + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter index " + "${resharerIndexes[i]}" + "'s resharer", + focusNodes[i], + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: fieldIsEmptyFlags[i] + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + !fieldIsEmptyFlags[i] + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Resharer Field Input.", + key: Key( + "frostResharerClearButtonKey_$i"), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Resharer Field Input.", + key: Key( + "frostResharerPasteButtonKey_$i"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + controllers[i].text = + data.text!.trim(); + } + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + child: fieldIsEmptyFlags[i] + ? const ClipboardIcon() + : const XIcon(), + ), + if (fieldIsEmptyFlags[i]) + TextFieldIconButton( + semanticsLabel: "Scan QR Button. " + "Opens Camera For Scanning QR Code.", + key: Key( + "frostCommitmentsScanQrButtonKey_$i"), + onTap: () async { + try { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await BarcodeScanner.scan(); + + controllers[i].text = + qrResult.rawContent; + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions " + "while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: "Continue", + enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e), + onPressed: _onPressed, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart new file mode 100644 index 000000000..cf3f9344b --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart @@ -0,0 +1,331 @@ +import 'dart:ffi'; + +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/services/frost.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/detail_item.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FrostReshareStep3abd extends ConsumerStatefulWidget { + const FrostReshareStep3abd({super.key}); + + static const String routeName = "/frostReshareStep3abd"; + static const String title = "Encryption keys"; + + @override + ConsumerState createState() => + _FrostReshareStep3abdState(); +} + +class _FrostReshareStep3abdState extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final List newParticipants; + late final int myIndex; + late final String? myEncryptionKey; + late final bool amOutgoingParticipant; + + final List fieldIsEmptyFlags = []; + + bool _buttonLock = false; + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + // collect encryptionKeys strings and insert my own at the correct index + final encryptionKeys = controllers.map((e) => e.text).toList(); + if (!amOutgoingParticipant) { + encryptionKeys.insert(myIndex, myEncryptionKey!); + } + + final result = Frost.finishResharer( + machine: ref.read(pFrostResharingData).startResharerData!.machine.ref, + encryptionKeysOfResharedTo: encryptionKeys, + ); + + ref.read(pFrostResharingData).resharerComplete = result; + + ref.read(pFrostCreateCurrentStep.state).state = 4; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } finally { + _buttonLock = false; + } + } + + @override + void initState() { + myEncryptionKey = + ref.read(pFrostResharingData).startResharedData?.resharedStart; + + newParticipants = ref.read(pFrostResharingData).configData!.newParticipants; + myIndex = newParticipants.indexOf(ref.read(pFrostResharingData).myName!); + + if (myIndex >= 0) { + // remove my name for now as we don't need a text field for it + newParticipants.removeAt(myIndex); + } + + if (myEncryptionKey == null && myIndex == -1) { + amOutgoingParticipant = true; + } else if (myEncryptionKey != null && myIndex >= 0) { + amOutgoingParticipant = false; + } else { + throw Exception("Invalid resharing state"); + } + + for (int i = 0; i < newParticipants.length; i++) { + controllers.add(TextEditingController()); + focusNodes.add(FocusNode()); + fieldIsEmptyFlags.add(true); + } + super.initState(); + } + + @override + void dispose() { + for (int i = 0; i < controllers.length; i++) { + controllers[i].dispose(); + } + for (int i = 0; i < focusNodes.length; i++) { + focusNodes[i].dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + if (!amOutgoingParticipant) + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: myEncryptionKey!, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + if (!amOutgoingParticipant) + const SizedBox( + height: 12, + ), + if (!amOutgoingParticipant) + DetailItem( + title: "My encryption key", + detail: myEncryptionKey!, + button: Util.isDesktop + ? IconCopyButton( + data: myEncryptionKey!, + ) + : SimpleCopyButton( + data: myEncryptionKey!, + ), + ), + if (!amOutgoingParticipant) + const SizedBox( + height: 12, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < newParticipants.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: Key("frostEncryptionKeyTextFieldKey_$i"), + controller: controllers[i], + focusNode: focusNodes[i], + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + onChanged: (_) { + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter " + "${newParticipants[i]}" + "'s encryption key", + focusNodes[i], + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: fieldIsEmptyFlags[i] + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + !fieldIsEmptyFlags[i] + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Encryption Key Field Input.", + key: Key( + "frostEncryptionKeyClearButtonKey_$i"), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Encryption Key Field Input.", + key: Key( + "frostEncryptionKeyPasteButtonKey_$i"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + controllers[i].text = + data.text!.trim(); + } + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + child: fieldIsEmptyFlags[i] + ? const ClipboardIcon() + : const XIcon(), + ), + if (fieldIsEmptyFlags[i]) + TextFieldIconButton( + semanticsLabel: "Scan QR Button. " + "Opens Camera For Scanning QR Code.", + key: Key( + "frostCommitmentsScanQrButtonKey_$i"), + onTap: () async { + try { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await BarcodeScanner.scan(); + + controllers[i].text = + qrResult.rawContent; + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions " + "while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Continue", + enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e), + onPressed: _onPressed, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart new file mode 100644 index 000000000..3c92fe11f --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/detail_item.dart'; + +class FrostReshareStep3c extends ConsumerStatefulWidget { + const FrostReshareStep3c({super.key}); + + static const String routeName = "/frostReshareStep3c"; + static const String title = "Encryption keys"; + + @override + ConsumerState createState() => _FrostReshareStep3bState(); +} + +class _FrostReshareStep3bState extends ConsumerState { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: ref + .watch(pFrostResharingData) + .startResharedData! + .resharedStart, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + const SizedBox( + height: 16, + ), + DetailItem( + title: "My encryption key", + detail: + ref.watch(pFrostResharingData).startResharedData!.resharedStart, + button: Util.isDesktop + ? IconCopyButton( + data: ref + .watch(pFrostResharingData) + .startResharedData! + .resharedStart, + ) + : SimpleCopyButton( + data: ref + .watch(pFrostResharingData) + .startResharedData! + .resharedStart, + ), + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: "Continue", + onPressed: () { + ref.read(pFrostCreateCurrentStep.state).state = 4; + Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart new file mode 100644 index 000000000..52789ebe2 --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart @@ -0,0 +1,369 @@ +import 'dart:ffi'; + +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/services/frost.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/detail_item.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +// was FinishResharingView +class FrostReshareStep4 extends ConsumerStatefulWidget { + const FrostReshareStep4({super.key}); + + static const String routeName = "/frostReshareStep4"; + static const String title = "Resharer completes"; + + @override + ConsumerState createState() => _FrostReshareStep4State(); +} + +class _FrostReshareStep4State extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final List resharerIndexes; + late final String myName; + late final int? myResharerIndexIndex; + late final String? myResharerComplete; + late final bool amOutgoingParticipant; + + final List fieldIsEmptyFlags = []; + + bool _buttonLock = false; + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + if (amOutgoingParticipant) { + ref.read(pFrostResharingData).reset(); + Navigator.of(context).popUntil( + ModalRoute.withName( + Util.isDesktop ? DesktopWalletView.routeName : WalletView.routeName, + ), + ); + } else { + // collect resharer completes strings and insert my own at the correct index + final resharerCompletes = controllers.map((e) => e.text).toList(); + if (myResharerIndexIndex != null && myResharerComplete != null) { + resharerCompletes.insert(myResharerIndexIndex!, myResharerComplete!); + } + + final data = Frost.finishReshared( + prior: ref.read(pFrostResharingData).startResharedData!.prior.ref, + resharerCompletes: resharerCompletes, + ); + + ref.read(pFrostResharingData).newWalletData = data; + + ref.read(pFrostCreateCurrentStep.state).state = 5; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + } + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + } finally { + _buttonLock = false; + } + } + + @override + void initState() { + final amNewParticipant = + ref.read(pFrostResharingData).startResharerData == null && + ref.read(pFrostResharingData).incompleteWallet != null && + ref.read(pFrostResharingData).incompleteWallet?.walletId == + ref.read(pFrostScaffoldArgs)!.walletId!; + + myName = ref.read(pFrostResharingData).myName!; + + resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; + + if (amNewParticipant) { + myResharerComplete = null; + myResharerIndexIndex = null; + amOutgoingParticipant = false; + } else { + myResharerComplete = ref.read(pFrostResharingData).resharerComplete!; + + final frostInfo = ref + .read(mainDBProvider) + .isar + .frostWalletInfo + .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!; + final myOldIndex = + frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!); + + myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex); + if (myResharerIndexIndex! >= 0) { + // remove my name for now as we don't need a text field for it + resharerIndexes.removeAt(myResharerIndexIndex!); + } + + amOutgoingParticipant = !ref + .read(pFrostResharingData) + .configData! + .newParticipants + .contains(ref.read(pFrostResharingData).myName!); + } + + for (int i = 0; i < resharerIndexes.length; i++) { + controllers.add(TextEditingController()); + focusNodes.add(FocusNode()); + fieldIsEmptyFlags.add(true); + } + super.initState(); + } + + @override + void dispose() { + for (int i = 0; i < controllers.length; i++) { + controllers[i].dispose(); + } + for (int i = 0; i < focusNodes.length; i++) { + focusNodes[i].dispose(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + if (myResharerComplete != null) + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: myResharerComplete!, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + if (myResharerComplete != null) + const SizedBox( + height: 16, + ), + if (myResharerComplete != null) + DetailItem( + title: "My resharer complete", + detail: myResharerComplete!, + button: Util.isDesktop + ? IconCopyButton( + data: myResharerComplete!, + ) + : SimpleCopyButton( + data: myResharerComplete!, + ), + ), + if (!amOutgoingParticipant) + const SizedBox( + height: 16, + ), + if (!amOutgoingParticipant) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < resharerIndexes.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: Key("frostEncryptionKeyTextFieldKey_$i"), + controller: controllers[i], + focusNode: focusNodes[i], + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + onChanged: (_) { + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter index " + "${resharerIndexes[i]}" + "'s resharer complete", + focusNodes[i], + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: fieldIsEmptyFlags[i] + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + !fieldIsEmptyFlags[i] + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Encryption Key Field Input.", + key: Key( + "frostEncryptionKeyClearButtonKey_$i"), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Encryption Key Field Input.", + key: Key( + "frostEncryptionKeyPasteButtonKey_$i"), + onTap: () async { + final ClipboardData? data = + await Clipboard.getData( + Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + controllers[i].text = + data.text!.trim(); + } + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i] + .text + .isEmpty; + }); + }, + child: fieldIsEmptyFlags[i] + ? const ClipboardIcon() + : const XIcon(), + ), + if (fieldIsEmptyFlags[i]) + TextFieldIconButton( + semanticsLabel: "Scan QR Button. " + "Opens Camera For Scanning QR Code.", + key: Key("frostScanQrButtonKey_$i"), + onTap: () async { + try { + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context) + .unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await BarcodeScanner.scan(); + + controllers[i].text = + qrResult.rawContent; + + setState(() { + fieldIsEmptyFlags[i] = + controllers[i].text.isEmpty; + }); + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions " + "while trying to scan qr code: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: amOutgoingParticipant ? "Exit" : "Complete", + enabled: amOutgoingParticipant || + !fieldIsEmptyFlags.reduce((v, e) => v |= e), + onPressed: _onPressed, + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart new file mode 100644 index 000000000..1e42a0f3e --- /dev/null +++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/providers/global/node_service_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/detail_item.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +// was VerifyUpdatedWalletView +class FrostReshareStep5 extends ConsumerStatefulWidget { + const FrostReshareStep5({super.key}); + + static const String routeName = "/frostReshareStep5"; + static const String title = "Verify"; + + @override + ConsumerState createState() => + _FrostReshareStep5State(); +} + +class _FrostReshareStep5State extends ConsumerState { + late final String config; + late final String serializedKeys; + late final String reshareId; + + late final bool isNew; + + bool _buttonLock = false; + Future _onPressed() async { + if (_buttonLock) { + return; + } + _buttonLock = true; + + try { + Exception? ex; + + final BitcoinFrostWallet wallet; + + if (isNew) { + wallet = await ref + .read(pFrostResharingData) + .incompleteWallet! + .toBitcoinFrostWallet( + mainDB: ref.read(mainDBProvider), + secureStorageInterface: ref.read(secureStoreProvider), + nodeService: ref.read(nodeServiceChangeNotifierProvider), + prefs: ref.read(prefsChangeNotifierProvider), + ); + + await wallet.info.setMnemonicVerified( + isar: ref.read(mainDBProvider).isar, + ); + + ref.read(pWallets).addWallet(wallet); + } else { + wallet = ref + .read(pWallets) + .getWallet(ref.read(pFrostScaffoldArgs)!.walletId!) + as BitcoinFrostWallet; + } + + if (mounted) { + await showLoading( + whileFuture: wallet.updateWithResharedData( + serializedKeys: serializedKeys, + multisigConfig: config, + isNewWallet: isNew, + ), + context: context, + message: isNew ? "Creating wallet" : "Updating wallet data", + isDesktop: Util.isDesktop, + onException: (e) => ex = e, + ); + + if (ex != null) { + throw ex!; + } + + if (mounted) { + ref.read(pFrostResharingData).reset(); + + Navigator.of(context).popUntil( + ModalRoute.withName( + _popUntilPath, + ), + ); + } + } + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + if (mounted) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + } finally { + _buttonLock = false; + } + } + + String get _popUntilPath => isNew + ? Util.isDesktop + ? DesktopHomeView.routeName + : HomeView.routeName + : Util.isDesktop + ? DesktopWalletView.routeName + : WalletView.routeName; + + @override + void initState() { + config = ref.read(pFrostResharingData).newWalletData!.multisigConfig; + serializedKeys = + ref.read(pFrostResharingData).newWalletData!.serializedKeys; + reshareId = ref.read(pFrostResharingData).newWalletData!.resharedId; + + isNew = ref.read(pFrostResharingData).incompleteWallet != null && + ref.read(pFrostResharingData).incompleteWallet!.walletId == + ref.read(pFrostScaffoldArgs)!.walletId!; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + "Ensure your reshare ID matches that of each other participant", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "ID", + detail: reshareId, + button: Util.isDesktop + ? IconCopyButton( + data: reshareId, + ) + : SimpleCopyButton( + data: reshareId, + ), + ), + const SizedBox( + height: 12, + ), + const SizedBox( + height: 12, + ), + Text( + "Back up your keys and config", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "Config", + detail: config, + button: Util.isDesktop + ? IconCopyButton( + data: config, + ) + : SimpleCopyButton( + data: config, + ), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "Keys", + detail: serializedKeys, + button: Util.isDesktop + ? IconCopyButton( + data: serializedKeys, + ) + : SimpleCopyButton( + data: serializedKeys, + ), + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Confirm", + onPressed: _onPressed, + ), + ], + ), + ); + } +} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart index 0a26cda75..6db2899dd 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart @@ -10,18 +10,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/initiate_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart'; +import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; +import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -146,9 +149,26 @@ class FrostMSWalletOptionsView extends ConsumerWidget { ref.read(pFrostMyName.state).state = frostInfo.myName; + final wallet = ref.read(pWallets).getWallet(walletId) + as BitcoinFrostWallet; + + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: null, // no wallet id yet + stepRoutes: FrostRouteGenerator.joinReshareStepRoutes, + onSuccess: () { + // successful completion of steps + // TODO + + ref.read(pFrostScaffoldArgs.state).state = null; + } + ); + Navigator.of(context).pushNamed( - ImportReshareConfigView.routeName, - arguments: walletId, + FrostStepScaffold.routeName, ); }, ), diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart similarity index 94% rename from lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart rename to lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart index caa77af5f..ffb1fab0b 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart @@ -4,10 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:frostdart/frostdart.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; +import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/format.dart'; @@ -15,6 +17,7 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; +import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -120,10 +123,25 @@ class _CompleteReshareConfigViewState ref.read(pFrostResharingData).myName = myName; ref.read(pFrostResharingData).resharerConfig = config; + final wallet = + ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; + + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: wallet.walletId, + stepRoutes: FrostRouteGenerator.initiateReshareStepRoutes, + onSuccess: () { + // successful completion of steps + // TODO + } + ); + if (mounted) { await Navigator.of(context).pushNamed( - DisplayReshareConfigView.routeName, - arguments: widget.walletId, + FrostStepScaffold.routeName, ); } } catch (e, s) { diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/initiate_resharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart similarity index 99% rename from lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/initiate_resharing_view.dart rename to lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart index 32818982c..87345370f 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/initiate_resharing_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart'; +import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart deleted file mode 100644 index a0ba76770..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart +++ /dev/null @@ -1,437 +0,0 @@ -import 'dart:ffi'; - -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; -import 'package:stackwallet/providers/db/main_db_provider.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/services/frost.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/detail_item.dart'; -import 'package:stackwallet/widgets/frost_mascot.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class FinishResharingView extends ConsumerStatefulWidget { - const FinishResharingView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/finishResharingView"; - - final String walletId; - - @override - ConsumerState createState() => - _FinishResharingViewState(); -} - -class _FinishResharingViewState extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final List resharerIndexes; - late final String myName; - late final int? myResharerIndexIndex; - late final String? myResharerComplete; - late final bool amOutgoingParticipant; - - final List fieldIsEmptyFlags = []; - - bool _buttonLock = false; - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - if (amOutgoingParticipant) { - ref.read(pFrostResharingData).reset(); - Navigator.of(context).popUntil( - ModalRoute.withName( - Util.isDesktop ? DesktopWalletView.routeName : WalletView.routeName, - ), - ); - } else { - // collect resharer completes strings and insert my own at the correct index - final resharerCompletes = controllers.map((e) => e.text).toList(); - if (myResharerIndexIndex != null && myResharerComplete != null) { - resharerCompletes.insert(myResharerIndexIndex!, myResharerComplete!); - } - - final data = Frost.finishReshared( - prior: ref.read(pFrostResharingData).startResharedData!.prior.ref, - resharerCompletes: resharerCompletes, - ); - - ref.read(pFrostResharingData).newWalletData = data; - - await Navigator.of(context).pushNamed( - VerifyUpdatedWalletView.routeName, - arguments: widget.walletId, - ); - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - if (mounted) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - } finally { - _buttonLock = false; - } - } - - @override - void initState() { - final amNewParticipant = - ref.read(pFrostResharingData).startResharerData == null && - ref.read(pFrostResharingData).incompleteWallet != null && - ref.read(pFrostResharingData).incompleteWallet?.walletId == - widget.walletId; - - myName = ref.read(pFrostResharingData).myName!; - - resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; - - if (amNewParticipant) { - myResharerComplete = null; - myResharerIndexIndex = null; - amOutgoingParticipant = false; - } else { - myResharerComplete = ref.read(pFrostResharingData).resharerComplete!; - - final frostInfo = ref - .read(mainDBProvider) - .isar - .frostWalletInfo - .getByWalletIdSync(widget.walletId)!; - final myOldIndex = - frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!); - - myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex); - if (myResharerIndexIndex! >= 0) { - // remove my name for now as we don't need a text field for it - resharerIndexes.removeAt(myResharerIndexIndex!); - } - - amOutgoingParticipant = !ref - .read(pFrostResharingData) - .configData! - .newParticipants - .contains(ref.read(pFrostResharingData).myName!); - } - - for (int i = 0; i < resharerIndexes.length; i++) { - controllers.add(TextEditingController()); - focusNodes.add(FocusNode()); - fieldIsEmptyFlags.add(true); - } - super.initState(); - } - - @override - void dispose() { - for (int i = 0; i < controllers.length; i++) { - controllers[i].dispose(); - } - for (int i = 0; i < focusNodes.length; i++) { - focusNodes[i].dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - trailing: FrostMascot( - title: 'Lorem ipsum', - body: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ', - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Resharer completes", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - if (myResharerComplete != null) - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: myResharerComplete!, - size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - if (myResharerComplete != null) const _Div(), - if (myResharerComplete != null) - DetailItem( - title: "My resharer complete", - detail: myResharerComplete!, - button: Util.isDesktop - ? IconCopyButton( - data: myResharerComplete!, - ) - : SimpleCopyButton( - data: myResharerComplete!, - ), - ), - if (!amOutgoingParticipant) const _Div(), - if (!amOutgoingParticipant) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < resharerIndexes.length; i++) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: Key("frostEncryptionKeyTextFieldKey_$i"), - controller: controllers[i], - focusNode: focusNodes[i], - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - onChanged: (_) { - setState(() { - fieldIsEmptyFlags[i] = - controllers[i].text.isEmpty; - }); - }, - decoration: standardInputDecoration( - "Enter index " - "${resharerIndexes[i]}" - "'s resharer complete", - focusNodes[i], - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: fieldIsEmptyFlags[i] - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - !fieldIsEmptyFlags[i] - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Encryption Key Field Input.", - key: Key( - "frostEncryptionKeyClearButtonKey_$i"), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Encryption Key Field Input.", - key: Key( - "frostEncryptionKeyPasteButtonKey_$i"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - controllers[i].text = - data.text!.trim(); - } - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - }, - child: fieldIsEmptyFlags[i] - ? const ClipboardIcon() - : const XIcon(), - ), - if (fieldIsEmptyFlags[i]) - TextFieldIconButton( - semanticsLabel: "Scan QR Button. " - "Opens Camera For Scanning QR Code.", - key: Key("frostScanQrButtonKey_$i"), - onTap: () async { - try { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); - } - - final qrResult = - await BarcodeScanner.scan(); - - controllers[i].text = - qrResult.rawContent; - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions " - "while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ], - ), - ], - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: amOutgoingParticipant ? "Exit" : "Complete", - enabled: amOutgoingParticipant || - !fieldIsEmptyFlags.reduce((v, e) => v |= e), - onPressed: _onPressed, - ), - ], - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart deleted file mode 100644 index a19d0ec75..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart +++ /dev/null @@ -1,401 +0,0 @@ -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:frostdart/frostdart.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart'; -import 'package:stackwallet/providers/db/main_db_provider.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/providers/global/secure_store_provider.dart'; -import 'package:stackwallet/services/frost.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/frost_step_user_steps.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class ImportReshareConfigView extends ConsumerStatefulWidget { - const ImportReshareConfigView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/importReshareConfigView"; - - final String walletId; - - @override - ConsumerState createState() => - _ImportReshareConfigViewState(); -} - -class _ImportReshareConfigViewState - extends ConsumerState { - static const info = [ - "Scan the config QR code or paste the code provided by the group member who" - " is initiating resharing.", - "Wait for other participants to finish importing the config.", - "Verify that everyone has filled out their forms before continuing. If you " - "try to continue before everyone is ready, the process will be canceled.", - "Check the box and press “Start resharing”.", - ]; - - late final TextEditingController configFieldController; - late final FocusNode configFocusNode; - - bool _configEmpty = true; - - bool _buttonLock = false; - bool _userVerifyContinue = false; - - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - // TODO: optimize this by creating watcher providers (similar to normal WalletInfo) - final frostInfo = ref - .read(mainDBProvider) - .isar - .frostWalletInfo - .getByWalletIdSync(widget.walletId)!; - - ref.read(pFrostResharingData).reset(); - ref.read(pFrostResharingData).myName = frostInfo.myName; - ref.read(pFrostResharingData).resharerConfig = configFieldController.text; - - String? salt; - try { - salt = Format.uint8listToString( - resharerSalt( - resharerConfig: ref.read(pFrostResharingData).resharerConfig!, - ), - ); - } catch (_) { - throw Exception("Bad resharer config"); - } - - if (frostInfo.knownSalts.contains(salt)) { - throw Exception("Duplicate config salt"); - } else { - final salts = frostInfo.knownSalts.toList(); - salts.add(salt); - final mainDB = ref.read(mainDBProvider); - await mainDB.isar.writeTxn(() async { - final id = frostInfo.id; - await mainDB.isar.frostWalletInfo.delete(id); - await mainDB.isar.frostWalletInfo.put( - frostInfo.copyWith(knownSalts: salts), - ); - }); - } - - final serializedKeys = await ref.read(secureStoreProvider).read( - key: "{${widget.walletId}}_serializedFROSTKeys", - ); - if (mounted) { - final result = Frost.beginResharer( - serializedKeys: serializedKeys!, - config: ref.read(pFrostResharingData).resharerConfig!, - ); - - ref.read(pFrostResharingData).startResharerData = result; - - await Navigator.of(context).pushNamed( - BeginResharingView.routeName, - arguments: widget.walletId, - ); - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - if (mounted) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - } finally { - _buttonLock = false; - } - } - - @override - void initState() { - configFieldController = TextEditingController(); - configFocusNode = FocusNode(); - super.initState(); - } - - @override - void dispose() { - configFieldController.dispose(); - configFocusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: const AppBarBackButton(), - title: Text( - "Import FROST reshare config", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 16, - ), - const FrostStepUserSteps( - userSteps: info, - ), - const SizedBox(height: 20), - Text( - "Enter config", - style: STextStyles.w500_14(context).copyWith( - color: - Theme.of(context).extension()!.textSubtitle1, - ), - ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("frConfigTextFieldKey"), - controller: configFieldController, - onChanged: (_) { - setState(() { - _configEmpty = configFieldController.text.isEmpty; - }); - }, - focusNode: configFocusNode, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter config", - configFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: _configEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - !_configEmpty - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Config Field.", - key: const Key("frConfigClearButtonKey"), - onTap: () { - configFieldController.text = ""; - - setState(() { - _configEmpty = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Config Field Input.", - key: const Key("frConfigPasteButtonKey"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - configFieldController.text = - data.text!.trim(); - } - - setState(() { - _configEmpty = - configFieldController.text.isEmpty; - }); - }, - child: _configEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (_configEmpty) - TextFieldIconButton( - semanticsLabel: - "Scan QR Button. Opens Camera For Scanning QR Code.", - key: const Key("frConfigScanQrButtonKey"), - onTap: () async { - try { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - - final qrResult = await BarcodeScanner.scan(); - - configFieldController.text = - qrResult.rawContent; - - setState(() { - _configEmpty = - configFieldController.text.isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ) - ], - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 16, - ), - if (!Util.isDesktop) const Spacer(), - const SizedBox( - height: 16, - ), - GestureDetector( - onTap: () { - setState(() { - _userVerifyContinue = !_userVerifyContinue; - }); - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 26, - child: Checkbox( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - value: _userVerifyContinue, - onChanged: (value) => setState( - () => _userVerifyContinue = value == true, - ), - ), - ), - const SizedBox( - width: 12, - ), - Expanded( - child: Text( - "I have verified that everyone has imported the config", - style: STextStyles.w500_14(context), - ), - ), - ], - ), - ), - ), - const SizedBox( - height: 16, - ), - PrimaryButton( - label: "Start resharing", - enabled: !_configEmpty && _userVerifyContinue, - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - } - - await _onPressed(); - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart deleted file mode 100644 index ebd92cc3c..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart +++ /dev/null @@ -1,439 +0,0 @@ -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; -import 'package:stackwallet/providers/db/main_db_provider.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/services/frost.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/detail_item.dart'; -import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class BeginResharingView extends ConsumerStatefulWidget { - const BeginResharingView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/beginResharingView"; - - final String walletId; - - @override - ConsumerState createState() => _BeginResharingViewState(); -} - -class _BeginResharingViewState extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final List resharerIndexes; - late final int myResharerIndexIndex; - late final String myResharerStart; - late final bool amOutgoingParticipant; - - final List fieldIsEmptyFlags = []; - - bool _buttonLock = false; - - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - if (!amOutgoingParticipant) { - // collect resharer strings - final resharerStarts = controllers.map((e) => e.text).toList(); - if (myResharerIndexIndex >= 0) { - // only insert my own at the correct index if I am a resharer - resharerStarts.insert(myResharerIndexIndex, myResharerStart); - } - - final result = Frost.beginReshared( - myName: ref.read(pFrostResharingData).myName!, - resharerConfig: ref.read(pFrostResharingData).resharerConfig!, - resharerStarts: resharerStarts, - ); - - ref.read(pFrostResharingData).startResharedData = result; - } - await Navigator.of(context).pushNamed( - ContinueResharingView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - if (mounted) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - } finally { - _buttonLock = false; - } - } - - @override - void initState() { - // TODO: optimize this by creating watcher providers (similar to normal WalletInfo) - final frostInfo = ref - .read(mainDBProvider) - .isar - .frostWalletInfo - .getByWalletIdSync(widget.walletId)!; - final myOldIndex = - frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!); - - myResharerStart = - ref.read(pFrostResharingData).startResharerData!.resharerStart; - - resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; - myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex); - if (myResharerIndexIndex >= 0) { - // remove my name for now as we don't need a text field for it - resharerIndexes.removeAt(myResharerIndexIndex); - } - - amOutgoingParticipant = !ref - .read(pFrostResharingData) - .configData! - .newParticipants - .contains(ref.read(pFrostResharingData).myName!); - - for (int i = 0; i < resharerIndexes.length; i++) { - controllers.add(TextEditingController()); - focusNodes.add(FocusNode()); - fieldIsEmptyFlags.add(true); - } - super.initState(); - } - - @override - void dispose() { - for (int i = 0; i < controllers.length; i++) { - controllers[i].dispose(); - } - for (int i = 0; i < focusNodes.length; i++) { - focusNodes[i].dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: Util.isDesktop - ? DesktopWalletView.routeName - : WalletView.routeName, - ), - ); - return false; - }, - child: ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: DesktopWalletView.routeName, - ), - ); - }, - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: WalletView.routeName, - ), - ); - }, - ), - title: Text( - "Resharers", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: myResharerStart, - size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const _Div(), - DetailItem( - title: "My resharer", - detail: myResharerStart, - button: Util.isDesktop - ? IconCopyButton( - data: myResharerStart, - ) - : SimpleCopyButton( - data: myResharerStart, - ), - ), - const _Div(), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < resharerIndexes.length; i++) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: Key("frostResharerTextFieldKey_$i"), - controller: controllers[i], - focusNode: focusNodes[i], - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - onChanged: (_) { - setState(() { - fieldIsEmptyFlags[i] = - controllers[i].text.isEmpty; - }); - }, - decoration: standardInputDecoration( - "Enter index " - "${resharerIndexes[i]}" - "'s resharer", - focusNodes[i], - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: fieldIsEmptyFlags[i] - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - !fieldIsEmptyFlags[i] - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Resharer Field Input.", - key: Key( - "frostResharerClearButtonKey_$i"), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Resharer Field Input.", - key: Key( - "frostResharerPasteButtonKey_$i"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - controllers[i].text = - data.text!.trim(); - } - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - }, - child: fieldIsEmptyFlags[i] - ? const ClipboardIcon() - : const XIcon(), - ), - if (fieldIsEmptyFlags[i]) - TextFieldIconButton( - semanticsLabel: "Scan QR Button. " - "Opens Camera For Scanning QR Code.", - key: Key( - "frostCommitmentsScanQrButtonKey_$i"), - onTap: () async { - try { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); - } - - final qrResult = - await BarcodeScanner.scan(); - - controllers[i].text = - qrResult.rawContent; - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions " - "while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ], - ), - ], - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Continue", - enabled: amOutgoingParticipant || - !fieldIsEmptyFlags.reduce((v, e) => v |= e), - onPressed: _onPressed, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart deleted file mode 100644 index b70139e69..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart +++ /dev/null @@ -1,429 +0,0 @@ -import 'dart:ffi'; - -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/services/frost.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/detail_item.dart'; -import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class ContinueResharingView extends ConsumerStatefulWidget { - const ContinueResharingView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/continueResharingView"; - - final String walletId; - - @override - ConsumerState createState() => - _ContinueResharingViewState(); -} - -class _ContinueResharingViewState extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final List newParticipants; - late final int myIndex; - late final String? myEncryptionKey; - late final bool amOutgoingParticipant; - - final List fieldIsEmptyFlags = []; - - bool _buttonLock = false; - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - // collect encryptionKeys strings and insert my own at the correct index - final encryptionKeys = controllers.map((e) => e.text).toList(); - if (!amOutgoingParticipant) { - encryptionKeys.insert(myIndex, myEncryptionKey!); - } - - final result = Frost.finishResharer( - machine: ref.read(pFrostResharingData).startResharerData!.machine.ref, - encryptionKeysOfResharedTo: encryptionKeys, - ); - - ref.read(pFrostResharingData).resharerComplete = result; - - await Navigator.of(context).pushNamed( - FinishResharingView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } finally { - _buttonLock = false; - } - } - - @override - void initState() { - myEncryptionKey = - ref.read(pFrostResharingData).startResharedData?.resharedStart; - - newParticipants = ref.read(pFrostResharingData).configData!.newParticipants; - myIndex = newParticipants.indexOf(ref.read(pFrostResharingData).myName!); - - if (myIndex >= 0) { - // remove my name for now as we don't need a text field for it - newParticipants.removeAt(myIndex); - } - - if (myEncryptionKey == null && myIndex == -1) { - amOutgoingParticipant = true; - } else if (myEncryptionKey != null && myIndex >= 0) { - amOutgoingParticipant = false; - } else { - throw Exception("Invalid resharing state"); - } - - for (int i = 0; i < newParticipants.length; i++) { - controllers.add(TextEditingController()); - focusNodes.add(FocusNode()); - fieldIsEmptyFlags.add(true); - } - super.initState(); - } - - @override - void dispose() { - for (int i = 0; i < controllers.length; i++) { - controllers[i].dispose(); - } - for (int i = 0; i < focusNodes.length; i++) { - focusNodes[i].dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: Util.isDesktop - ? DesktopWalletView.routeName - : WalletView.routeName, - ), - ); - return false; - }, - child: ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: DesktopWalletView.routeName, - ), - ); - }, - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: WalletView.routeName, - ), - ); - }, - ), - title: Text( - "Encryption keys", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - if (!amOutgoingParticipant) - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: myEncryptionKey!, - size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - if (!amOutgoingParticipant) const _Div(), - if (!amOutgoingParticipant) - DetailItem( - title: "My encryption key", - detail: myEncryptionKey!, - button: Util.isDesktop - ? IconCopyButton( - data: myEncryptionKey!, - ) - : SimpleCopyButton( - data: myEncryptionKey!, - ), - ), - if (!amOutgoingParticipant) const _Div(), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < newParticipants.length; i++) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: Key("frostEncryptionKeyTextFieldKey_$i"), - controller: controllers[i], - focusNode: focusNodes[i], - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - onChanged: (_) { - setState(() { - fieldIsEmptyFlags[i] = - controllers[i].text.isEmpty; - }); - }, - decoration: standardInputDecoration( - "Enter " - "${newParticipants[i]}" - "'s encryption key", - focusNodes[i], - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: fieldIsEmptyFlags[i] - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - !fieldIsEmptyFlags[i] - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Encryption Key Field Input.", - key: Key( - "frostEncryptionKeyClearButtonKey_$i"), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Encryption Key Field Input.", - key: Key( - "frostEncryptionKeyPasteButtonKey_$i"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - controllers[i].text = - data.text!.trim(); - } - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - }, - child: fieldIsEmptyFlags[i] - ? const ClipboardIcon() - : const XIcon(), - ), - if (fieldIsEmptyFlags[i]) - TextFieldIconButton( - semanticsLabel: "Scan QR Button. " - "Opens Camera For Scanning QR Code.", - key: Key( - "frostCommitmentsScanQrButtonKey_$i"), - onTap: () async { - try { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); - } - - final qrResult = - await BarcodeScanner.scan(); - - controllers[i].text = - qrResult.rawContent; - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions " - "while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ], - ), - ], - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Continue", - enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e), - onPressed: _onPressed, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart deleted file mode 100644 index 622dace0b..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/pages/home_view/home_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/detail_item.dart'; -import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart'; -import 'package:stackwallet/widgets/frost_mascot.dart'; - -class NewContinueSharingView extends ConsumerStatefulWidget { - const NewContinueSharingView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/NewContinueSharingView"; - - final String walletId; - - @override - ConsumerState createState() => - _NewContinueSharingViewState(); -} - -class _NewContinueSharingViewState - extends ConsumerState { - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: - Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName, - ), - ); - return false; - }, - child: ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: DesktopHomeView.routeName, - ), - ); - }, - ), - trailing: FrostMascot( - title: 'Lorem ipsum', - body: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ', - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: HomeView.routeName, - ), - ); - }, - ), - title: Text( - "Encryption keys", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: ref - .watch(pFrostResharingData) - .startResharedData! - .resharedStart, - size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const _Div(), - DetailItem( - title: "My encryption key", - detail: ref - .watch(pFrostResharingData) - .startResharedData! - .resharedStart, - button: Util.isDesktop - ? IconCopyButton( - data: ref - .watch(pFrostResharingData) - .startResharedData! - .resharedStart, - ) - : SimpleCopyButton( - data: ref - .watch(pFrostResharingData) - .startResharedData! - .resharedStart, - ), - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Continue", - onPressed: () { - Navigator.of(context).pushNamed( - FinishResharingView.routeName, - arguments: widget.walletId, - ); - }, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart deleted file mode 100644 index ec3f00408..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart +++ /dev/null @@ -1,431 +0,0 @@ -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/show_loading.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart'; -import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; -import 'package:stackwallet/wallets/models/incomplete_frost_wallet.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/frost_mascot.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class NewImportResharerConfigView extends ConsumerStatefulWidget { - const NewImportResharerConfigView({ - super.key, - required this.walletName, - required this.frostCurrency, - }); - - static const String routeName = "/newImportResharerConfigView"; - - final String walletName; - final FrostCurrency frostCurrency; - - @override - ConsumerState createState() => - _NewImportResharerConfigViewState(); -} - -class _NewImportResharerConfigViewState - extends ConsumerState { - late final TextEditingController myNameFieldController, configFieldController; - late final FocusNode myNameFocusNode, configFocusNode; - - bool _nameEmpty = true, _configEmpty = true; - - bool _buttonLock = false; - - Future _createWallet() async { - final info = WalletInfo.createNew( - name: widget.walletName, - coin: widget.frostCurrency.coin, - ); - - final wallet = IncompleteFrostWallet(); - wallet.info = info; - - return wallet; - } - - @override - void initState() { - myNameFieldController = TextEditingController(); - configFieldController = TextEditingController(); - myNameFocusNode = FocusNode(); - configFocusNode = FocusNode(); - super.initState(); - } - - @override - void dispose() { - myNameFieldController.dispose(); - configFieldController.dispose(); - myNameFocusNode.dispose(); - configFocusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - // TODO: [prio=high] get rid of placeholder text?? - trailing: FrostMascot( - title: 'Lorem ipsum', - body: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ', - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Import FROST reshare config", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 16, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("frMyNameTextFieldKey"), - controller: myNameFieldController, - onChanged: (_) { - setState(() { - _nameEmpty = myNameFieldController.text.isEmpty; - }); - }, - focusNode: myNameFocusNode, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "My name", - myNameFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: _nameEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - !_nameEmpty - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Config Field.", - key: const Key("frMyNameClearButtonKey"), - onTap: () { - myNameFieldController.text = ""; - - setState(() { - _nameEmpty = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Name Field.", - key: const Key("frMyNamePasteButtonKey"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - myNameFieldController.text = - data.text!.trim(); - } - - setState(() { - _nameEmpty = - myNameFieldController.text.isEmpty; - }); - }, - child: _nameEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 16, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("frConfigTextFieldKey"), - controller: configFieldController, - onChanged: (_) { - setState(() { - _configEmpty = configFieldController.text.isEmpty; - }); - }, - focusNode: configFocusNode, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter config", - configFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: _configEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - !_configEmpty - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Config Field.", - key: const Key("frConfigClearButtonKey"), - onTap: () { - configFieldController.text = ""; - - setState(() { - _configEmpty = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Config Field Input.", - key: const Key("frConfigPasteButtonKey"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - configFieldController.text = - data.text!.trim(); - } - - setState(() { - _configEmpty = - configFieldController.text.isEmpty; - }); - }, - child: _configEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (_configEmpty) - TextFieldIconButton( - semanticsLabel: - "Scan QR Button. Opens Camera For Scanning QR Code.", - key: const Key("frConfigScanQrButtonKey"), - onTap: () async { - try { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75)); - } - - final qrResult = await BarcodeScanner.scan(); - - configFieldController.text = - qrResult.rawContent; - - setState(() { - _configEmpty = - configFieldController.text.isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ) - ], - ), - ), - ), - ), - ), - ), - const SizedBox( - height: 16, - ), - if (!Util.isDesktop) const Spacer(), - const SizedBox( - height: 16, - ), - PrimaryButton( - label: "Start", - enabled: !_nameEmpty && !_configEmpty, - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - } - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - ref.read(pFrostResharingData).reset(); - ref.read(pFrostResharingData).myName = - myNameFieldController.text; - ref.read(pFrostResharingData).resharerConfig = - configFieldController.text; - - if (!ref - .read(pFrostResharingData) - .configData! - .newParticipants - .contains(ref.read(pFrostResharingData).myName!)) { - ref.read(pFrostResharingData).reset(); - return await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "My name not found in config participants", - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - - Exception? ex; - final wallet = await showLoading( - whileFuture: _createWallet(), - context: context, - message: "Setting up wallet", - isDesktop: Util.isDesktop, - onException: (e) => ex = e, - ); - - if (ex != null) { - throw ex!; - } - - if (context.mounted) { - ref.read(pFrostResharingData).incompleteWallet = wallet!; - await Navigator.of(context).pushNamed( - NewStartResharingView.routeName, - arguments: wallet.walletId, - ); - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - if (context.mounted) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - } finally { - _buttonLock = false; - } - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart deleted file mode 100644 index 8bf618235..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart +++ /dev/null @@ -1,373 +0,0 @@ -import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/home_view/home_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/services/frost.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart'; -import 'package:stackwallet/widgets/frost_mascot.dart'; -import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; - -class NewStartResharingView extends ConsumerStatefulWidget { - const NewStartResharingView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/newStartResharingView"; - - final String walletId; - - @override - ConsumerState createState() => - _NewStartResharingViewState(); -} - -class _NewStartResharingViewState extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final List resharerIndexes; - - final List fieldIsEmptyFlags = []; - - bool _buttonLock = false; - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - // collect resharer strings - final resharerStarts = controllers.map((e) => e.text).toList(); - - final result = Frost.beginReshared( - myName: ref.read(pFrostResharingData).myName!, - resharerConfig: ref.read(pFrostResharingData).resharerConfig!, - resharerStarts: resharerStarts, - ); - - ref.read(pFrostResharingData).startResharedData = result; - - await Navigator.of(context).pushNamed( - NewContinueSharingView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } finally { - _buttonLock = false; - } - } - - @override - void initState() { - resharerIndexes = ref.read(pFrostResharingData).configData!.resharers; - - for (int i = 0; i < resharerIndexes.length; i++) { - controllers.add(TextEditingController()); - focusNodes.add(FocusNode()); - fieldIsEmptyFlags.add(true); - } - super.initState(); - } - - @override - void dispose() { - for (int i = 0; i < controllers.length; i++) { - controllers[i].dispose(); - } - for (int i = 0; i < focusNodes.length; i++) { - focusNodes[i].dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: - Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName, - ), - ); - return false; - }, - child: ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: DesktopHomeView.routeName, - ), - ); - }, - ), - trailing: FrostMascot( - title: 'Lorem ipsum', - body: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ', - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => const FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: HomeView.routeName, - ), - ); - }, - ), - title: Text( - "Resharers", - style: STextStyles.navBarTitle(context), - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < resharerIndexes.length; i++) - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: Key("frostResharerTextFieldKey_$i"), - controller: controllers[i], - focusNode: focusNodes[i], - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - onChanged: (_) { - setState(() { - fieldIsEmptyFlags[i] = - controllers[i].text.isEmpty; - }); - }, - decoration: standardInputDecoration( - "Enter index " - "${resharerIndexes[i]}" - "'s resharer", - focusNodes[i], - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, - ), - suffixIcon: Padding( - padding: fieldIsEmptyFlags[i] - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - !fieldIsEmptyFlags[i] - ? TextFieldIconButton( - semanticsLabel: - "Clear Button. Clears The Resharer Field Input.", - key: Key( - "frostResharerClearButtonKey_$i"), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Resharer Field Input.", - key: Key( - "frostResharerPasteButtonKey_$i"), - onTap: () async { - final ClipboardData? data = - await Clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - controllers[i].text = - data.text!.trim(); - } - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - }, - child: fieldIsEmptyFlags[i] - ? const ClipboardIcon() - : const XIcon(), - ), - if (fieldIsEmptyFlags[i]) - TextFieldIconButton( - semanticsLabel: "Scan QR Button. " - "Opens Camera For Scanning QR Code.", - key: Key( - "frostCommitmentsScanQrButtonKey_$i"), - onTap: () async { - try { - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); - } - - final qrResult = - await BarcodeScanner.scan(); - - controllers[i].text = - qrResult.rawContent; - - setState(() { - fieldIsEmptyFlags[i] = - controllers[i] - .text - .isEmpty; - }); - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions " - "while trying to scan qr code: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], - ), - ), - ), - ), - ), - ), - ), - ], - ), - ], - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Continue", - enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e), - onPressed: _onPressed, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart deleted file mode 100644 index c2507a2fc..000000000 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart +++ /dev/null @@ -1,315 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/home_view/home_view.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; -import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; -import 'package:stackwallet/providers/db/main_db_provider.dart'; -import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; -import 'package:stackwallet/providers/global/node_service_provider.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/providers/global/secure_store_provider.dart'; -import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/show_loading.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; -import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; -import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/desktop/primary_button.dart'; -import 'package:stackwallet/widgets/detail_item.dart'; -import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; - -class VerifyUpdatedWalletView extends ConsumerStatefulWidget { - const VerifyUpdatedWalletView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/verifyUpdatedWalletView"; - - final String walletId; - - @override - ConsumerState createState() => - _VerifyUpdatedWalletViewState(); -} - -class _VerifyUpdatedWalletViewState - extends ConsumerState { - late final String config; - late final String serializedKeys; - late final String reshareId; - - late final bool isNew; - - bool _buttonLock = false; - Future _onPressed() async { - if (_buttonLock) { - return; - } - _buttonLock = true; - - try { - Exception? ex; - - final BitcoinFrostWallet wallet; - - if (isNew) { - wallet = await ref - .read(pFrostResharingData) - .incompleteWallet! - .toBitcoinFrostWallet( - mainDB: ref.read(mainDBProvider), - secureStorageInterface: ref.read(secureStoreProvider), - nodeService: ref.read(nodeServiceChangeNotifierProvider), - prefs: ref.read(prefsChangeNotifierProvider), - ); - - await wallet.info.setMnemonicVerified( - isar: ref.read(mainDBProvider).isar, - ); - - ref.read(pWallets).addWallet(wallet); - } else { - wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; - } - - if (mounted) { - await showLoading( - whileFuture: wallet.updateWithResharedData( - serializedKeys: serializedKeys, - multisigConfig: config, - isNewWallet: isNew, - ), - context: context, - message: isNew ? "Creating wallet" : "Updating wallet data", - isDesktop: Util.isDesktop, - onException: (e) => ex = e, - ); - - if (ex != null) { - throw ex!; - } - - if (mounted) { - ref.read(pFrostResharingData).reset(); - - Navigator.of(context).popUntil( - ModalRoute.withName( - _popUntilPath, - ), - ); - } - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - if (mounted) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - } finally { - _buttonLock = false; - } - } - - String get _popUntilPath => isNew - ? Util.isDesktop - ? DesktopHomeView.routeName - : HomeView.routeName - : Util.isDesktop - ? DesktopWalletView.routeName - : WalletView.routeName; - - @override - void initState() { - config = ref.read(pFrostResharingData).newWalletData!.multisigConfig; - serializedKeys = - ref.read(pFrostResharingData).newWalletData!.serializedKeys; - reshareId = ref.read(pFrostResharingData).newWalletData!.resharedId; - - isNew = ref.read(pFrostResharingData).incompleteWallet != null && - ref.read(pFrostResharingData).incompleteWallet!.walletId == - widget.walletId; - - super.initState(); - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: _popUntilPath, - ), - ); - return false; - }, - child: ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: _popUntilPath, - ), - ); - }, - ), - trailing: ExitToMyStackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: _popUntilPath, - ), - ); - }, - ), - ), - body: SizedBox( - width: 480, - child: child, - ), - ), - child: ConditionalParent( - condition: !Util.isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - await showDialog( - context: context, - builder: (_) => FrostInterruptionDialog( - type: FrostInterruptionDialogType.resharing, - popUntilOnYesRouteName: _popUntilPath, - ), - ); - }, - ), - ), - body: SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - Text( - "Ensure your reshare ID matches that of each other participant", - style: STextStyles.pageTitleH2(context), - ), - const _Div(), - DetailItem( - title: "ID", - detail: reshareId, - button: Util.isDesktop - ? IconCopyButton( - data: reshareId, - ) - : SimpleCopyButton( - data: reshareId, - ), - ), - const _Div(), - const _Div(), - Text( - "Back up your keys and config", - style: STextStyles.pageTitleH2(context), - ), - const _Div(), - DetailItem( - title: "Config", - detail: config, - button: Util.isDesktop - ? IconCopyButton( - data: config, - ) - : SimpleCopyButton( - data: config, - ), - ), - const _Div(), - DetailItem( - title: "Keys", - detail: serializedKeys, - button: Util.isDesktop - ? IconCopyButton( - data: serializedKeys, - ) - : SimpleCopyButton( - data: serializedKeys, - ), - ), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Confirm", - onPressed: _onPressed, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index 594a586ea..28402ebfb 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -123,17 +123,8 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_pr import 'package:stackwallet/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/initiate_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart'; -import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart'; +import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart'; +import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart'; @@ -499,52 +490,6 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case NewImportResharerConfigView.routeName: - if (args is ({ - String walletName, - FrostCurrency frostCurrency, - })) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewImportResharerConfigView( - walletName: args.walletName, - frostCurrency: args.frostCurrency, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case NewStartResharingView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewStartResharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case NewContinueSharingView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => NewContinueSharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - case FrostStepScaffold.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -582,20 +527,6 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case ImportReshareConfigView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ImportReshareConfigView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - case InitiateResharingView.routeName: if (args is String) { return getRoute( @@ -625,76 +556,6 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case DisplayReshareConfigView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => DisplayReshareConfigView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case BeginResharingView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => BeginResharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case ContinueResharingView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => ContinueResharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case FinishResharingView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FinishResharingView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case VerifyUpdatedWalletView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => VerifyUpdatedWalletView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - case FrostSendView.routeName: if (args is ({ String walletId,