From 48e05919d59b58479d6e37ecdf6edbe1827bc15c Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 1 May 2024 14:30:33 -0600 Subject: [PATCH] use frost step scaffold for frost send/sign flow --- .../new/steps => }/frost_route_generator.dart | 54 +++ .../new/create_new_frost_ms_wallet_view.dart | 4 +- .../select_new_frost_import_type_view.dart | 4 +- .../new/steps/frost_create_step_1a.dart | 2 +- .../new/steps/frost_create_step_1b.dart | 2 +- .../new/steps/frost_create_step_2.dart | 2 +- .../new/steps/frost_create_step_3.dart | 2 +- .../new/steps/frost_create_step_4.dart | 2 +- .../new/steps/frost_create_step_5.dart | 2 +- .../reshare/frost_reshare_step_1a.dart | 2 +- .../reshare/frost_reshare_step_1b.dart | 2 +- .../reshare/frost_reshare_step_1c.dart | 2 +- .../reshare/frost_reshare_step_2abd.dart | 2 +- .../reshare/frost_reshare_step_2c.dart | 2 +- .../reshare/frost_reshare_step_3abd.dart | 2 +- .../reshare/frost_reshare_step_3c.dart | 2 +- .../reshare/frost_reshare_step_4.dart | 2 +- .../reshare/frost_reshare_step_5.dart | 5 +- .../frost_attempt_sign_config_view.dart | 404 ---------------- .../frost_ms/frost_complete_sign_view.dart | 206 -------- .../frost_continue_sign_config_view.dart | 445 ------------------ .../frost_create_sign_config_view.dart | 185 -------- .../frost_import_sign_config_view.dart | 332 ------------- .../send_view/frost_ms/frost_send_view.dart | 24 +- .../send_steps/frost_send_step_1a.dart | 196 ++++++++ .../send_steps/frost_send_step_1b.dart | 180 +++++++ .../send_steps/frost_send_step_2.dart | 407 ++++++++++++++++ .../send_steps/frost_send_step_3.dart | 345 ++++++++++++++ .../send_steps/frost_send_step_4.dart | 144 ++++++ .../frost_ms/frost_ms_options_view.dart | 4 +- .../complete_reshare_config_view.dart | 4 +- lib/pages/wallet_view/wallet_view.dart | 22 +- .../wallet_view/sub_widgets/my_wallet.dart | 37 +- lib/route_generator.dart | 47 +- .../frost_ms => widgets}/frost_scaffold.dart | 2 +- 35 files changed, 1422 insertions(+), 1657 deletions(-) rename lib/{pages/add_wallet_views/frost_ms/new/steps => }/frost_route_generator.dart (78%) delete mode 100644 lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart delete mode 100644 lib/pages/send_view/frost_ms/frost_complete_sign_view.dart delete mode 100644 lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart delete mode 100644 lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart delete mode 100644 lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart create mode 100644 lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart create mode 100644 lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart create mode 100644 lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart create mode 100644 lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart create mode 100644 lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart rename lib/{pages/add_wallet_views/frost_ms => widgets}/frost_scaffold.dart (98%) diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart b/lib/frost_route_generator.dart similarity index 78% rename from lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart rename to lib/frost_route_generator.dart index e180259ea..c8b06f916 100644 --- a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart +++ b/lib/frost_route_generator.dart @@ -16,6 +16,11 @@ import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshar 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/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart'; +import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart'; +import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart'; +import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart'; +import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart'; @@ -85,6 +90,20 @@ abstract class FrostRouteGenerator { (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title), ]; + static const List sendFrostTxStepRoutes = [ + (routeName: FrostSendStep1a.routeName, title: FrostSendStep1a.title), + (routeName: FrostSendStep2.routeName, title: FrostSendStep2.title), + (routeName: FrostSendStep3.routeName, title: FrostSendStep3.title), + (routeName: FrostSendStep4.routeName, title: FrostSendStep4.title), + ]; + + static const List signFrostTxStepRoutes = [ + (routeName: FrostSendStep1b.routeName, title: FrostSendStep1b.title), + (routeName: FrostSendStep2.routeName, title: FrostSendStep2.title), + (routeName: FrostSendStep3.routeName, title: FrostSendStep3.title), + (routeName: FrostSendStep4.routeName, title: FrostSendStep4.title), + ]; + static Route generateRoute(RouteSettings settings) { final args = settings.arguments; @@ -194,6 +213,41 @@ abstract class FrostRouteGenerator { settings: settings, ); + case FrostSendStep1a.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostSendStep1a(), + settings: settings, + ); + + case FrostSendStep1b.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostSendStep1b(), + settings: settings, + ); + + case FrostSendStep2.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostSendStep2(), + settings: settings, + ); + + case FrostSendStep3.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostSendStep3(), + settings: settings, + ); + + case FrostSendStep4.routeName: + return RouteGenerator.getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const FrostSendStep4(), + settings: settings, + ); + default: return _routeError(""); } 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 f9f3b8caf..4a9af99f6 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 @@ -3,9 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/frost_route_generator.dart'; 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_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; @@ -24,6 +23,7 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart'; import 'package:stackwallet/widgets/frost_mascot.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; 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 ddf01aacb..1abcc74cd 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 @@ -3,9 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/frost_route_generator.dart'; 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_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -22,6 +21,7 @@ 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/simple_mobile_dialog.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class SelectNewFrostImportTypeView extends ConsumerStatefulWidget { 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 8c292c574..6e0f1aab2 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,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/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/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'; 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 f95991f3e..3b34d18cc 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 @@ -1,6 +1,6 @@ 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/frost_route_generator.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/utilities/text_styles.dart'; 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 259059adb..4bce49625 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 @@ -1,7 +1,7 @@ import 'package:flutter/material.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_route_generator.dart'; +import 'package:stackwallet/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'; import 'package:stackwallet/services/frost.dart'; 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 d48107055..58ae379ec 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 @@ -1,7 +1,7 @@ import 'package:flutter/material.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_route_generator.dart'; +import 'package:stackwallet/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'; import 'package:stackwallet/services/frost.dart'; 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 93fff4f77..864e905bf 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,7 @@ 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_route_generator.dart'; +import 'package:stackwallet/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'; import 'package:stackwallet/utilities/util.dart'; 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 6b7a674c0..5210ed269 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 @@ -3,7 +3,7 @@ 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_route_generator.dart'; +import 'package:stackwallet/frost_route_generator.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.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/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart index 9f819b760..7534322d3 100644 --- a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.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/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/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'; 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 index 81815fff8..23853546f 100644 --- 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 @@ -1,7 +1,7 @@ import 'package:flutter/material.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/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'; 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 index b46cd3587..9e30f4ab9 100644 --- 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 @@ -2,7 +2,7 @@ import 'dart:async'; 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/frost_route_generator.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/show_loading.dart'; 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 index bd2b5c8ce..8f4b33a09 100644 --- 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 @@ -3,7 +3,7 @@ import 'dart:async'; 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/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'; 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 index 6ba4dd5ca..0c811c635 100644 --- 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 @@ -2,7 +2,7 @@ import 'dart:async'; 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/frost_route_generator.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/utilities/logger.dart'; 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 index ba365d08a..9eb87a874 100644 --- 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 @@ -3,7 +3,7 @@ import 'dart:ffi'; 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/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'; 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 index 3c92fe11f..226caa544 100644 --- 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 @@ -1,7 +1,7 @@ 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/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'; 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 index 8a6c3e302..cb56fbfc2 100644 --- 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 @@ -3,7 +3,7 @@ import 'dart:ffi'; 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/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'; 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 index 1e42a0f3e..10c7a6a24 100644 --- 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 @@ -1,6 +1,6 @@ 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/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'; @@ -30,8 +30,7 @@ class FrostReshareStep5 extends ConsumerStatefulWidget { static const String title = "Verify"; @override - ConsumerState createState() => - _FrostReshareStep5State(); + ConsumerState createState() => _FrostReshareStep5State(); } class _FrostReshareStep5State extends ConsumerState { diff --git a/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart deleted file mode 100644 index 149eb61d3..000000000 --- a/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart +++ /dev/null @@ -1,404 +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/send_view/frost_ms/frost_continue_sign_config_view.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/providers/global/wallets_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/logger.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/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 FrostAttemptSignConfigView extends ConsumerStatefulWidget { - const FrostAttemptSignConfigView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/frostAttemptSignConfigView"; - - final String walletId; - - @override - ConsumerState createState() => - _FrostAttemptSignConfigViewState(); -} - -class _FrostAttemptSignConfigViewState - extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final String myName; - late final List participantsWithoutMe; - late final String myPreprocess; - late final int myIndex; - late final int threshold; - - final List fieldIsEmptyFlags = []; - - bool hasEnoughPreprocesses() { - // own preprocess is not included in controllers and must be set here - int count = 1; - - for (final controller in controllers) { - if (controller.text.isNotEmpty) { - count++; - } - } - - return count >= threshold; - } - - @override - void initState() { - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; - final frostInfo = wallet.frostInfo; - - myName = frostInfo.myName; - threshold = frostInfo.threshold; - participantsWithoutMe = List.from(frostInfo.participants); // Copy so it isn't fixed-length. - myIndex = participantsWithoutMe.indexOf(frostInfo.myName); - myPreprocess = ref.read(pFrostAttemptSignData.state).state!.preprocess; - - participantsWithoutMe.removeAt(myIndex); - - for (int i = 0; i < participantsWithoutMe.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: 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( - "Preprocesses", - 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: [ - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: myPreprocess, - size: 220, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const _Div(), - DetailItem( - title: "My name", - detail: myName, - ), - const _Div(), - DetailItem( - title: "My preprocess", - detail: myPreprocess, - button: Util.isDesktop - ? IconCopyButton( - data: myPreprocess, - ) - : SimpleCopyButton( - data: myPreprocess, - ), - ), - const _Div(), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < participantsWithoutMe.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("frostPreprocessesTextFieldKey_$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 ${participantsWithoutMe[i]}'s preprocess", - 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 Preprocess Field Input.", - key: Key( - "frostPreprocessesClearButtonKey_$i", - ), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From Clipboard To Preprocess Field Input.", - key: Key( - "frostPreprocessesPasteButtonKey_$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( - "frostPreprocessesScanQrButtonKey_$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 signing", - enabled: hasEnoughPreprocesses(), - onPressed: () async { - // collect Preprocess strings (not including my own) - final preprocesses = controllers.map((e) => e.text).toList(); - - // collect participants who are involved in this transaction - final List requiredParticipantsUnordered = []; - for (int i = 0; i < participantsWithoutMe.length; i++) { - if (preprocesses[i].isNotEmpty) { - requiredParticipantsUnordered.add(participantsWithoutMe[i]); - } - } - ref.read(pFrostSelectParticipantsUnordered.notifier).state = - requiredParticipantsUnordered; - - // insert an empty string at my index - preprocesses.insert(myIndex, ""); - - try { - ref.read(pFrostContinueSignData.notifier).state = - Frost.continueSigning( - machinePtr: - ref.read(pFrostAttemptSignData.state).state!.machinePtr, - preprocesses: preprocesses, - ); - - await Navigator.of(context).pushNamed( - FrostContinueSignView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - return await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to continue signing", - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - }, - ), - ], - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart b/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart deleted file mode 100644 index 6478495c0..000000000 --- a/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart +++ /dev/null @@ -1,206 +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/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/my_stack_view.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/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/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/stack_dialog.dart'; - -class FrostCompleteSignView extends ConsumerStatefulWidget { - const FrostCompleteSignView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/frostCompleteSignView"; - - final String walletId; - - @override - ConsumerState createState() => - _FrostCompleteSignViewState(); -} - -class _FrostCompleteSignViewState extends ConsumerState { - bool _broadcastLock = false; - - @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( - "Preview transaction", - 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: [ - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: ref.watch(pFrostTxData.state).state!.raw!, - size: 220, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const _Div(), - DetailItem( - title: "Raw transaction hex", - detail: ref.watch(pFrostTxData.state).state!.raw!, - button: Util.isDesktop - ? IconCopyButton( - data: ref.watch(pFrostTxData.state).state!.raw!, - ) - : SimpleCopyButton( - data: ref.watch(pFrostTxData.state).state!.raw!, - ), - ), - const _Div(), - if (!Util.isDesktop) const Spacer(), - const _Div(), - PrimaryButton( - label: "Broadcast Transaction", - onPressed: () async { - if (_broadcastLock) { - return; - } - _broadcastLock = true; - - try { - Exception? ex; - final txData = await showLoading( - whileFuture: ref - .read(pWallets) - .getWallet(widget.walletId) - .confirmSend( - txData: ref.read(pFrostTxData.state).state!, - ), - context: context, - message: "Broadcasting transaction to network", - isDesktop: Util.isDesktop, - onException: (e) { - ex = e; - }, - ); - - if (ex != null) { - throw ex!; - } - - if (mounted) { - if (txData != null) { - ref.read(pFrostTxData.state).state = txData; - Navigator.of(context).popUntil( - ModalRoute.withName( - Util.isDesktop - ? MyStackView.routeName - : WalletView.routeName, - ), - ); - } - } - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - return await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Broadcast error", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } finally { - _broadcastLock = false; - } - }, - ), - ], - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart deleted file mode 100644 index 8bfa512ca..000000000 --- a/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart +++ /dev/null @@ -1,445 +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/send_view/frost_ms/frost_complete_sign_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/providers/global/wallets_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/logger.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/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 FrostContinueSignView extends ConsumerStatefulWidget { - const FrostContinueSignView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/frostContinueSignView"; - - final String walletId; - - @override - ConsumerState createState() => - _FrostContinueSignViewState(); -} - -class _FrostContinueSignViewState extends ConsumerState { - final List controllers = []; - final List focusNodes = []; - - late final String myName; - late final List participantsWithoutMe; - late final List participantsAll; - late final String myShare; - late final int myIndex; - - final List fieldIsEmptyFlags = []; - - @override - void initState() { - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; - - final frostInfo = wallet.frostInfo; - - myName = frostInfo.myName; - participantsAll = frostInfo.participants; - myIndex = frostInfo.participants.indexOf(frostInfo.myName); - myShare = ref.read(pFrostContinueSignData.state).state!.share; - - participantsWithoutMe = frostInfo.participants - .toSet() - .intersection( - ref.read(pFrostSelectParticipantsUnordered.state).state!.toSet()) - .toList(); - - participantsWithoutMe.remove(myName); - - for (int i = 0; i < participantsWithoutMe.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.transactionCreation, - 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.transactionCreation, - 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.transactionCreation, - popUntilOnYesRouteName: WalletView.routeName, - ), - ); - }, - ), - title: Text( - "Shares", - 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: [ - SizedBox( - height: 220, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: myShare, - size: 220, - backgroundColor: Theme.of(context) - .extension()! - .background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - const _Div(), - DetailItem( - title: "My name", - detail: myName, - ), - const _Div(), - DetailItem( - title: "My shares", - detail: myShare, - button: Util.isDesktop - ? IconCopyButton( - data: myShare, - ) - : SimpleCopyButton( - data: myShare, - ), - ), - const _Div(), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (int i = 0; i < participantsWithoutMe.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("frostSharesTextFieldKey_$i"), - controller: controllers[i], - focusNode: focusNodes[i], - readOnly: false, - autocorrect: false, - enableSuggestions: false, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter ${participantsWithoutMe[i]}'s share", - 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 Share Field Input.", - key: Key( - "frostSharesClearButtonKey_$i", - ), - onTap: () { - controllers[i].text = ""; - - setState(() { - fieldIsEmptyFlags[i] = true; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - semanticsLabel: - "Paste Button. Pastes From " - "Clipboard To Share Field Input.", - key: Key( - "frostSharesPasteButtonKey_$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( - "frostSharesScanQrButtonKey_$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: "Complete signing", - onPressed: () async { - // check for empty shares - if (controllers - .map((e) => e.text.isEmpty) - .reduce((value, element) => value |= element)) { - return await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Missing Shares", - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - - // collect Share strings - final sharesCollected = - controllers.map((e) => e.text).toList(); - - final List shares = []; - for (final participant in participantsAll) { - if (participantsWithoutMe.contains(participant)) { - shares.add(sharesCollected[ - participantsWithoutMe.indexOf(participant)]); - } else { - shares.add(""); - } - } - - try { - final rawTx = Frost.completeSigning( - machinePtr: ref - .read(pFrostContinueSignData.state) - .state! - .machinePtr, - shares: shares, - ); - - ref.read(pFrostTxData.state).state = - ref.read(pFrostTxData.state).state!.copyWith( - raw: rawTx, - ); - - await Navigator.of(context).pushNamed( - FrostCompleteSignView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Fatal, - ); - - return await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to complete signing process", - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } - }, - ), - ], - ), - ), - ), - ); - } -} - -class _Div extends StatelessWidget { - const _Div({super.key}); - - @override - Widget build(BuildContext context) { - return const SizedBox( - height: 12, - ); - } -} diff --git a/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart deleted file mode 100644 index bb2c129ca..000000000 --- a/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart +++ /dev/null @@ -1,185 +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/send_view/frost_ms/frost_attempt_sign_config_view.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/providers/global/wallets_provider.dart'; -import 'package:stackwallet/themes/stack_colors.dart'; -import 'package:stackwallet/utilities/logger.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'; - -class FrostCreateSignConfigView extends ConsumerStatefulWidget { - const FrostCreateSignConfigView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/frostCreateSignConfigView"; - - final String walletId; - - @override - ConsumerState createState() => - _FrostCreateSignConfigViewState(); -} - -class _FrostCreateSignConfigViewState - extends ConsumerState { - bool _attemptSignLock = false; - - Future _attemptSign() async { - if (_attemptSignLock) { - return; - } - - _attemptSignLock = true; - - try { - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; - - final attemptSignRes = await wallet.frostAttemptSignConfig( - config: ref.read(pFrostTxData.state).state!.frostMSConfig!, - ); - - ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes; - - await Navigator.of(context).pushNamed( - FrostAttemptSignConfigView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); - } finally { - _attemptSignLock = false; - } - } - - @override - Widget build(BuildContext context) { - double qrImageSize = - Util.isDesktop ? 360 : MediaQuery.of(context).size.width - 32; - return ConditionalParent( - condition: Util.isDesktop, - builder: (child) => DesktopScaffold( - background: Theme.of(context).extension()!.background, - appBar: const DesktopAppBar( - isCompactHeight: false, - leading: AppBarBackButton(), - ), - body: SingleChildScrollView( - child: SizedBox( - width: 600, // Was 480, may look better but overflows the bottom. - 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( - "Sign 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.symmetric(horizontal: 16), - child: child, - ), - ), - ), - ); - }, - ), - ), - ), - ), - child: Column( - children: [ - if (!Util.isDesktop) const Spacer(), - SizedBox( - height: qrImageSize, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - QrImageView( - data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, - size: qrImageSize, - backgroundColor: - Theme.of(context).extension()!.background, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ], - ), - ), - if (!Util.isDesktop) - const SizedBox( - height: 32, - ), - DetailItem( - title: "Encoded config", - detail: ref.watch(pFrostTxData.state).state!.frostMSConfig!, - button: Util.isDesktop - ? IconCopyButton( - data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, - ) - : SimpleCopyButton( - data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, - ), - ), - SizedBox( - height: Util.isDesktop ? 20 : 16, - ), - if (!Util.isDesktop) - const Spacer( - flex: 2, - ), - PrimaryButton( - label: "Attempt sign", - onPressed: () { - _attemptSign(); - }, - ), - const SizedBox( - height: 16, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart deleted file mode 100644 index c89b6846a..000000000 --- a/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart +++ /dev/null @@ -1,332 +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:isar/isar.dart'; -import 'package:stackwallet/models/isar/models/isar_models.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_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/wallets_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/models/tx_data.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/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/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 FrostImportSignConfigView extends ConsumerStatefulWidget { - const FrostImportSignConfigView({ - super.key, - required this.walletId, - }); - - static const String routeName = "/frostImportSignConfigView"; - - final String walletId; - - @override - ConsumerState createState() => - _FrostImportSignConfigViewState(); -} - -class _FrostImportSignConfigViewState - extends ConsumerState { - late final TextEditingController configFieldController; - late final FocusNode configFocusNode; - - bool _configEmpty = true; - - bool _attemptSignLock = false; - - Future _attemptSign() async { - if (_attemptSignLock) { - return; - } - - _attemptSignLock = true; - - try { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - } - - final config = configFieldController.text; - final wallet = - ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet; - - final data = Frost.extractDataFromSignConfig( - signConfig: config, - coin: wallet.cryptoCurrency, - ); - - final utxos = await ref - .read(mainDBProvider) - .getUTXOs(wallet.walletId) - .filter() - .anyOf( - data.inputs, - (q, e) => q - .txidEqualTo(Format.uint8listToString(e.hash)) - .and() - .valueEqualTo(e.value) - .and() - .voutEqualTo(e.vout)) - .findAll(); - - // TODO add more data from 'data' and display to user ? - ref.read(pFrostTxData.notifier).state = TxData( - frostMSConfig: config, - recipients: data.recipients - .map((e) => (address: e.address, amount: e.amount, isChange: false)) - .toList(), - utxos: utxos.toSet(), - ); - - final attemptSignRes = await wallet.frostAttemptSignConfig( - config: ref.read(pFrostTxData.state).state!.frostMSConfig!, - ); - - ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes; - - await Navigator.of(context).pushNamed( - FrostAttemptSignConfigView.routeName, - arguments: widget.walletId, - ); - } catch (e, s) { - Logging.instance.log( - "$e\n$s", - level: LogLevel.Error, - ); - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Import and attempt sign config failed", - message: e.toString(), - desktopPopRootNavigator: Util.isDesktop, - ), - ); - } finally { - _attemptSignLock = 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: AppBarBackButton( - onPressed: () { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Import FROST sign 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("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 signing", - enabled: !_configEmpty, - onPressed: () { - _attemptSign(); - }, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/send_view/frost_ms/frost_send_view.dart b/lib/pages/send_view/frost_ms/frost_send_view.dart index 93488a43f..0dde93687 100644 --- a/lib/pages/send_view/frost_ms/frost_send_view.dart +++ b/lib/pages/send_view/frost_ms/frost_send_view.dart @@ -14,9 +14,9 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/frost_route_generator.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_create_sign_config_view.dart'; import 'package:stackwallet/pages/send_view/frost_ms/recipient.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -38,6 +38,7 @@ import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/fee_slider.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -120,12 +121,29 @@ class _FrostSendViewState extends ConsumerState { ); } + final wallet = + ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet; + if (mounted && txData != null) { ref.read(pFrostTxData.notifier).state = txData; + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: walletId, + stepRoutes: FrostRouteGenerator.sendFrostTxStepRoutes, + onSuccess: () { + // successful completion of steps + // TODO ? + + ref.read(pFrostScaffoldArgs.state).state = null; + } + ); + await Navigator.of(context).pushNamed( - FrostCreateSignConfigView.routeName, - arguments: widget.walletId, + FrostStepScaffold.routeName, ); } } catch (e) { diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart new file mode 100644 index 000000000..fd83a05a0 --- /dev/null +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/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/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/logger.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/rounded_white_container.dart'; + +class FrostSendStep1a extends ConsumerStatefulWidget { + const FrostSendStep1a({super.key}); + + static const String routeName = "/FrostSendStep1a"; + static const String title = "FROST transaction"; + + @override + ConsumerState createState() => _FrostSendStep1aState(); +} + +class _FrostSendStep1aState extends ConsumerState { + static const steps2to4 = [ + "Wait for them to import the transaction 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 “Attempt sign”.", + ]; + + bool _attemptSignLock = false; + + Future _attemptSign() async { + if (_attemptSignLock) { + return; + } + + _attemptSignLock = true; + + try { + final wallet = ref.read(pWallets).getWallet( + ref.read(pFrostScaffoldArgs)!.walletId!, + ) as BitcoinFrostWallet; + + final attemptSignRes = await wallet.frostAttemptSignConfig( + config: ref.read(pFrostTxData.state).state!.frostMSConfig!, + ); + + ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes; + + 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.Error, + ); + } finally { + _attemptSignLock = false; + } + } + + @override + Widget build(BuildContext context) { + final double qrImageSize = + Util.isDesktop ? 360 : MediaQuery.of(context).size.width - 32; + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "1.", + style: STextStyles.w500_12(context), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + "Share this config with the group members. ", + style: STextStyles.w600_12(context), + ), + TextSpan( + text: + "You must have the threshold number of signatures (including yours) to send the transaction.", + style: STextStyles.w600_12(context).copyWith( + color: Theme.of(context) + .extension()! + .customTextButtonEnabledText, + ), + ), + ], + ), + ), + ), + ], + ), + for (int i = 0; i < steps2to4.length; i++) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${i + 2}.", + style: STextStyles.w500_12(context), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: Text( + steps2to4[i], + style: STextStyles.w500_12(context), + ), + ), + ], + ), + ], + ), + ), + SizedBox( + height: Util.isDesktop ? 20 : 16, + ), + SizedBox( + height: qrImageSize, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, + size: qrImageSize, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + if (!Util.isDesktop) + const SizedBox( + height: 32, + ), + DetailItem( + title: "Encoded config", + detail: ref.watch(pFrostTxData.state).state!.frostMSConfig!, + button: Util.isDesktop + ? IconCopyButton( + data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, + ) + : SimpleCopyButton( + data: ref.watch(pFrostTxData.state).state!.frostMSConfig!, + ), + ), + SizedBox( + height: Util.isDesktop ? 20 : 16, + ), + if (!Util.isDesktop) + const Spacer( + flex: 2, + ), + PrimaryButton( + label: "Attempt sign", + onPressed: () { + _attemptSign(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart new file mode 100644 index 000000000..94f367507 --- /dev/null +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/frost_route_generator.dart'; +import 'package:stackwallet/models/isar/models/isar_models.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/utilities/format.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/models/tx_data.dart'; +import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/frost_step_user_steps.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/textfields/frost_step_field.dart'; + +class FrostSendStep1b extends ConsumerStatefulWidget { + const FrostSendStep1b({super.key}); + + static const String routeName = "/FrostSendStep1b"; + static const String title = "Sign FROST transaction"; + + @override + ConsumerState createState() => _FrostSendStep1bState(); +} + +class _FrostSendStep1bState extends ConsumerState { + static const info = [ + "Scan the config QR code or paste the code provided by the member " + "initiating this transaction.", + "Wait for other members 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 will be " + "canceled.", + "Check the box and press “Start signing”.", + ]; + + late final TextEditingController configFieldController; + late final FocusNode configFocusNode; + + bool _configEmpty = true; + + bool _attemptSignLock = false; + + Future _attemptSign() async { + if (_attemptSignLock) { + return; + } + + _attemptSignLock = true; + + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + } + + final config = configFieldController.text; + final wallet = ref.read(pWallets).getWallet( + ref.read(pFrostScaffoldArgs)!.walletId!, + ) as BitcoinFrostWallet; + + final data = Frost.extractDataFromSignConfig( + signConfig: config, + coin: wallet.cryptoCurrency, + ); + + final utxos = await ref + .read(mainDBProvider) + .getUTXOs(wallet.walletId) + .filter() + .anyOf( + data.inputs, + (q, e) => q + .txidEqualTo(Format.uint8listToString(e.hash)) + .and() + .valueEqualTo(e.value) + .and() + .voutEqualTo(e.vout)) + .findAll(); + + // TODO add more data from 'data' and display to user ? + ref.read(pFrostTxData.notifier).state = TxData( + frostMSConfig: config, + recipients: data.recipients + .map((e) => (address: e.address, amount: e.amount, isChange: false)) + .toList(), + utxos: utxos.toSet(), + ); + + final attemptSignRes = await wallet.frostAttemptSignConfig( + config: ref.read(pFrostTxData.state).state!.frostMSConfig!, + ); + + ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes; + + 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.Error, + ); + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Import and attempt sign config failed", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } finally { + _attemptSignLock = 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( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const FrostStepUserSteps( + userSteps: info, + ), + const SizedBox(height: 12), + FrostStepField( + controller: configFieldController, + focusNode: configFocusNode, + showQrScanOption: true, + label: "Import sign config", + hint: "Enter config", + onChanged: (_) { + setState(() { + _configEmpty = configFieldController.text.isEmpty; + }); + }, + ), + const SizedBox( + height: 16, + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: "Start signing", + enabled: !_configEmpty, + onPressed: () { + _attemptSign(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart new file mode 100644 index 000000000..06394c2a6 --- /dev/null +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart @@ -0,0 +1,407 @@ +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/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/providers/global/wallets_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/logger.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/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/rounded_white_container.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 FrostSendStep2 extends ConsumerStatefulWidget { + const FrostSendStep2({super.key}); + + static const String routeName = "/FrostSendStep2"; + static const String title = "Preprocesses"; + + @override + ConsumerState createState() => _FrostSendStep2State(); +} + +class _FrostSendStep2State extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final String myName; + late final List participantsWithoutMe; + late final String myPreprocess; + late final int myIndex; + late final int threshold; + + final List fieldIsEmptyFlags = []; + + bool hasEnoughPreprocesses() { + // own preprocess is not included in controllers and must be set here + int count = 1; + + for (final controller in controllers) { + if (controller.text.isNotEmpty) { + count++; + } + } + + return count >= threshold; + } + + @override + void initState() { + final wallet = ref.read(pWallets).getWallet( + ref.read(pFrostScaffoldArgs)!.walletId!, + ) as BitcoinFrostWallet; + final frostInfo = wallet.frostInfo; + + myName = frostInfo.myName; + threshold = frostInfo.threshold; + participantsWithoutMe = + List.from(frostInfo.participants); // Copy so it isn't fixed-length. + myIndex = participantsWithoutMe.indexOf(frostInfo.myName); + myPreprocess = ref.read(pFrostAttemptSignData.state).state!.preprocess; + + participantsWithoutMe.removeAt(myIndex); + + for (int i = 0; i < participantsWithoutMe.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( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "1.", + style: STextStyles.w500_12(context), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: Text( + "Share your preprocess with other signing group members.", + style: STextStyles.w500_12(context), + ), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "1.", + style: STextStyles.w500_12(context), + ), + const SizedBox( + width: 4, + ), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + "Enter their preprocesses into the corresponding fields. ", + style: STextStyles.w600_12(context), + ), + TextSpan( + text: "You must have the threshold number of " + "preprocesses (including yours) to send this transaction.", + style: STextStyles.w600_12(context).copyWith( + color: Theme.of(context) + .extension()! + .customTextButtonEnabledText, + ), + ), + ], + ), + ), + ), + ], + ), + ], + ), + ), + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: myPreprocess, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "My name", + detail: myName, + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "My preprocess", + detail: myPreprocess, + button: Util.isDesktop + ? IconCopyButton( + data: myPreprocess, + ) + : SimpleCopyButton( + data: myPreprocess, + ), + ), + const SizedBox( + height: 12, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < participantsWithoutMe.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("frostPreprocessesTextFieldKey_$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 ${participantsWithoutMe[i]}'s preprocess", + 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 Preprocess Field Input.", + key: Key( + "frostPreprocessesClearButtonKey_$i", + ), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Preprocess Field Input.", + key: Key( + "frostPreprocessesPasteButtonKey_$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( + "frostPreprocessesScanQrButtonKey_$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 signing", + enabled: hasEnoughPreprocesses(), + onPressed: () async { + // collect Preprocess strings (not including my own) + final preprocesses = controllers.map((e) => e.text).toList(); + + // collect participants who are involved in this transaction + final List requiredParticipantsUnordered = []; + for (int i = 0; i < participantsWithoutMe.length; i++) { + if (preprocesses[i].isNotEmpty) { + requiredParticipantsUnordered.add(participantsWithoutMe[i]); + } + } + ref.read(pFrostSelectParticipantsUnordered.notifier).state = + requiredParticipantsUnordered; + + // insert an empty string at my index + preprocesses.insert(myIndex, ""); + + try { + ref.read(pFrostContinueSignData.notifier).state = + Frost.continueSigning( + machinePtr: + ref.read(pFrostAttemptSignData.state).state!.machinePtr, + preprocesses: preprocesses, + ); + + ref.read(pFrostCreateCurrentStep.state).state = 3; + await Navigator.of(context).pushNamed( + ref + .read(pFrostScaffoldArgs)! + .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1] + .routeName, + ); + + // await Navigator.of(context).pushNamed( + // FrostContinueSignView.routeName, + // arguments: widget.walletId, + // ); + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + return await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to continue signing", + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart new file mode 100644 index 000000000..25a39b13b --- /dev/null +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart @@ -0,0 +1,345 @@ +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/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/providers/global/wallets_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/logger.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/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 FrostSendStep3 extends ConsumerStatefulWidget { + const FrostSendStep3({super.key}); + + static const String routeName = "/FrostSendStep3"; + static const String title = "Shares"; + + @override + ConsumerState createState() => _FrostSendStep3State(); +} + +class _FrostSendStep3State extends ConsumerState { + final List controllers = []; + final List focusNodes = []; + + late final String myName; + late final List participantsWithoutMe; + late final List participantsAll; + late final String myShare; + late final int myIndex; + + final List fieldIsEmptyFlags = []; + + @override + void initState() { + final wallet = ref.read(pWallets).getWallet( + ref.read(pFrostScaffoldArgs)!.walletId!, + ) as BitcoinFrostWallet; + + final frostInfo = wallet.frostInfo; + + myName = frostInfo.myName; + participantsAll = frostInfo.participants; + myIndex = frostInfo.participants.indexOf(frostInfo.myName); + myShare = ref.read(pFrostContinueSignData.state).state!.share; + + participantsWithoutMe = frostInfo.participants + .toSet() + .intersection( + ref.read(pFrostSelectParticipantsUnordered.state).state!.toSet()) + .toList(); + + participantsWithoutMe.remove(myName); + + for (int i = 0; i < participantsWithoutMe.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( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: myShare, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "My name", + detail: myName, + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "My shares", + detail: myShare, + button: Util.isDesktop + ? IconCopyButton( + data: myShare, + ) + : SimpleCopyButton( + data: myShare, + ), + ), + const SizedBox( + height: 12, + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < participantsWithoutMe.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("frostSharesTextFieldKey_$i"), + controller: controllers[i], + focusNode: focusNodes[i], + readOnly: false, + autocorrect: false, + enableSuggestions: false, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${participantsWithoutMe[i]}'s share", + 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 Share Field Input.", + key: Key( + "frostSharesClearButtonKey_$i", + ), + onTap: () { + controllers[i].text = ""; + + setState(() { + fieldIsEmptyFlags[i] = true; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From " + "Clipboard To Share Field Input.", + key: Key( + "frostSharesPasteButtonKey_$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( + "frostSharesScanQrButtonKey_$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: "Complete signing", + onPressed: () async { + // check for empty shares + if (controllers + .map((e) => e.text.isEmpty) + .reduce((value, element) => value |= element)) { + return await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Missing Shares", + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + + // collect Share strings + final sharesCollected = controllers.map((e) => e.text).toList(); + + final List shares = []; + for (final participant in participantsAll) { + if (participantsWithoutMe.contains(participant)) { + shares.add(sharesCollected[ + participantsWithoutMe.indexOf(participant)]); + } else { + shares.add(""); + } + } + + try { + final rawTx = Frost.completeSigning( + machinePtr: + ref.read(pFrostContinueSignData.state).state!.machinePtr, + shares: shares, + ); + + ref.read(pFrostTxData.state).state = + ref.read(pFrostTxData.state).state!.copyWith( + raw: rawTx, + ); + + 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, + ); + + return await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to complete signing process", + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart new file mode 100644 index 000000000..315bbb9a9 --- /dev/null +++ b/lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/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/my_stack_view.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/logger.dart'; +import 'package:stackwallet/utilities/show_loading.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/stack_dialog.dart'; + +class FrostSendStep4 extends ConsumerStatefulWidget { + const FrostSendStep4({super.key}); + + static const String routeName = "/FrostSendStep4"; + static const String title = "Preview transaction"; + + @override + ConsumerState createState() => _FrostSendStep4State(); +} + +class _FrostSendStep4State extends ConsumerState { + bool _broadcastLock = false; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox( + height: 220, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + QrImageView( + data: ref.watch(pFrostTxData.state).state!.raw!, + size: 220, + backgroundColor: + Theme.of(context).extension()!.background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + DetailItem( + title: "Raw transaction hex", + detail: ref.watch(pFrostTxData.state).state!.raw!, + button: Util.isDesktop + ? IconCopyButton( + data: ref.watch(pFrostTxData.state).state!.raw!, + ) + : SimpleCopyButton( + data: ref.watch(pFrostTxData.state).state!.raw!, + ), + ), + const SizedBox( + height: 12, + ), + if (!Util.isDesktop) const Spacer(), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Broadcast Transaction", + onPressed: () async { + if (_broadcastLock) { + return; + } + _broadcastLock = true; + + try { + Exception? ex; + final txData = await showLoading( + whileFuture: ref + .read(pWallets) + .getWallet( + ref.read(pFrostScaffoldArgs)!.walletId!, + ) + .confirmSend( + txData: ref.read(pFrostTxData.state).state!, + ), + context: context, + message: "Broadcasting transaction to network", + isDesktop: Util.isDesktop, + onException: (e) { + ex = e; + }, + ); + + if (ex != null) { + throw ex!; + } + + if (mounted) { + if (txData != null) { + ref.read(pFrostTxData.state).state = txData; + Navigator.of(context).popUntil( + ModalRoute.withName( + Util.isDesktop + ? MyStackView.routeName + : WalletView.routeName, + ), + ); + } + } + } catch (e, s) { + Logging.instance.log( + "$e\n$s", + level: LogLevel.Fatal, + ); + + return await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Broadcast error", + message: e.toString(), + desktopPopRootNavigator: Util.isDesktop, + ), + ); + } finally { + _broadcastLock = false; + } + }, + ), + ], + ), + ); + } +} 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 02484dc63..16eda73fd 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,8 +10,7 @@ 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/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/initiate_resharing/initiate_resharing_view.dart'; @@ -30,6 +29,7 @@ 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/frost_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class FrostMSWalletOptionsView extends ConsumerWidget { diff --git a/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart index 03cffcb51..6a92b76ed 100644 --- a/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart @@ -4,8 +4,7 @@ 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/frost_scaffold.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart'; +import 'package:stackwallet/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'; @@ -24,6 +23,7 @@ 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_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 79a9fac7c..57c5c977f 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:isar/isar.dart'; +import 'package:stackwallet/frost_route_generator.dart'; import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart'; @@ -29,7 +30,6 @@ import 'package:stackwallet/pages/ordinals/ordinals_view.dart'; import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart'; import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; @@ -79,6 +79,7 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/small_tor_icon.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -359,9 +360,24 @@ class _WalletViewState extends ConsumerState { } Future _onFrostSignPressed(BuildContext context) async { + final wallet = ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet; + ref.read(pFrostScaffoldArgs.state).state = ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: walletId, + stepRoutes: FrostRouteGenerator.signFrostTxStepRoutes, + onSuccess: () { + // successful completion of steps + // TODO ? + + ref.read(pFrostScaffoldArgs.state).state = null; + } + ); + await Navigator.of(context).pushNamed( - FrostImportSignConfigView.routeName, - arguments: walletId, + FrostStepScaffold.routeName, ); } diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index 5b0cac5f7..22cf282af 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart'; +import 'package:stackwallet/frost_route_generator.dart'; import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart'; @@ -21,14 +21,15 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart'; import 'package:stackwallet/widgets/custom_tab_view.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class MyWallet extends ConsumerStatefulWidget { const MyWallet({ - Key? key, + super.key, required this.walletId, this.contractAddress, - }) : super(key: key); + }); final String walletId; final String? contractAddress; @@ -85,10 +86,32 @@ class _MyWalletState extends ConsumerState { width: 200, buttonHeight: ButtonHeight.l, label: "Import sign config", - onPressed: () { - Navigator.of(context).pushNamed( - FrostImportSignConfigView.routeName, - arguments: widget.walletId, + onPressed: () async { + final wallet = ref + .read(pWallets) + .getWallet(widget.walletId) + as BitcoinFrostWallet; + ref.read(pFrostScaffoldArgs.state).state = + ( + info: ( + walletName: wallet.info.name, + frostCurrency: wallet.cryptoCurrency, + ), + walletId: widget.walletId, + stepRoutes: FrostRouteGenerator + .signFrostTxStepRoutes, + onSuccess: () { + // successful completion of steps + // TODO ? + + ref + .read(pFrostScaffoldArgs.state) + .state = null; + } + ); + + await Navigator.of(context).pushNamed( + FrostStepScaffold.routeName, ); }, ), diff --git a/lib/route_generator.dart b/lib/route_generator.dart index d60c60de3..f6dcaa6da 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -26,7 +26,6 @@ import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_tok import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; -import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart'; import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart'; @@ -80,9 +79,6 @@ import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.d import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_create_sign_config_view.dart'; -import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart'; import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/send_view/token_send_view.dart'; @@ -197,6 +193,7 @@ import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency. import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/widgets/choose_coin_view.dart'; +import 'package:stackwallet/widgets/frost_scaffold.dart'; import 'package:tuple/tuple.dart'; /* @@ -574,48 +571,6 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); - case FrostImportSignConfigView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostImportSignConfigView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case FrostCreateSignConfigView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostCreateSignConfigView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - - case FrostAttemptSignConfigView.routeName: - if (args is String) { - return getRoute( - shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => FrostAttemptSignConfigView( - walletId: args, - ), - settings: RouteSettings( - name: settings.name, - ), - ); - } - return _routeError("${settings.name} invalid args: ${args.toString()}"); - // case MonkeyLoadedView.routeName: // if (args is Tuple2>) { // return getRoute( diff --git a/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart b/lib/widgets/frost_scaffold.dart similarity index 98% rename from lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart rename to lib/widgets/frost_scaffold.dart index b2f2dcbe3..34ab2b922 100644 --- a/lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart +++ b/lib/widgets/frost_scaffold.dart @@ -1,6 +1,6 @@ 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/frost_route_generator.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart';