use frost step scaffold for frost send/sign flow

This commit is contained in:
julian 2024-05-01 14:30:33 -06:00
parent 92d0733005
commit 48e05919d5
35 changed files with 1422 additions and 1657 deletions

View file

@ -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_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_4.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.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/route_generator.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart'; import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
@ -85,6 +90,20 @@ abstract class FrostRouteGenerator {
(routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title), (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title),
]; ];
static const List<FrostStepRoute> 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<FrostStepRoute> 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<dynamic> generateRoute(RouteSettings settings) { static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments; final args = settings.arguments;
@ -194,6 +213,41 @@ abstract class FrostRouteGenerator {
settings: settings, 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: default:
return _routeError(""); return _routeError("");
} }

View file

@ -3,9 +3,8 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/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/home_view/home_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/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/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart'; import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
import 'package:stackwallet/widgets/frost_mascot.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/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';

View file

@ -3,9 +3,8 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.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/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/home_view/home_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_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'; 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/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.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'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class SelectNewFrostImportTypeView extends ConsumerStatefulWidget { class SelectNewFrostImportTypeView extends ConsumerStatefulWidget {

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.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/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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.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/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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';

View file

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';

View file

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/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/db/main_db_provider.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:frostdart/frostdart.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/db/main_db_provider.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';
import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/logger.dart';

View file

@ -3,7 +3,7 @@ import 'dart:ffi';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart'; import 'package:stackwallet/services/frost.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';

View file

@ -3,7 +3,7 @@ import 'dart:ffi';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.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/transaction_views/transaction_details_view.dart';
import 'package:stackwallet/pages/wallet_view/wallet_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/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/home_view/home_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_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/wallet_view/wallet_view.dart';
@ -30,8 +30,7 @@ class FrostReshareStep5 extends ConsumerStatefulWidget {
static const String title = "Verify"; static const String title = "Verify";
@override @override
ConsumerState<FrostReshareStep5> createState() => ConsumerState<FrostReshareStep5> createState() => _FrostReshareStep5State();
_FrostReshareStep5State();
} }
class _FrostReshareStep5State extends ConsumerState<FrostReshareStep5> { class _FrostReshareStep5State extends ConsumerState<FrostReshareStep5> {

View file

@ -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<FrostAttemptSignConfigView> createState() =>
_FrostAttemptSignConfigViewState();
}
class _FrostAttemptSignConfigViewState
extends ConsumerState<FrostAttemptSignConfigView> {
final List<TextEditingController> controllers = [];
final List<FocusNode> focusNodes = [];
late final String myName;
late final List<String> participantsWithoutMe;
late final String myPreprocess;
late final int myIndex;
late final int threshold;
final List<bool> 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<StackColors>()!.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<StackColors>()!.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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>.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<String> 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<void>(
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,
);
}
}

View file

@ -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<FrostCompleteSignView> createState() =>
_FrostCompleteSignViewState();
}
class _FrostCompleteSignViewState extends ConsumerState<FrostCompleteSignView> {
bool _broadcastLock = false;
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => DesktopScaffold(
background: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>(
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,
);
}
}

View file

@ -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<FrostContinueSignView> createState() =>
_FrostContinueSignViewState();
}
class _FrostContinueSignViewState extends ConsumerState<FrostContinueSignView> {
final List<TextEditingController> controllers = [];
final List<FocusNode> focusNodes = [];
late final String myName;
late final List<String> participantsWithoutMe;
late final List<String> participantsAll;
late final String myShare;
late final int myIndex;
final List<bool> 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<void>(
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<StackColors>()!.background,
appBar: DesktopAppBar(
isCompactHeight: false,
leading: AppBarBackButton(
onPressed: () async {
await showDialog<void>(
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<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
await showDialog<void>(
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<StackColors>()!
.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>.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<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Missing Shares",
desktopPopRootNavigator: Util.isDesktop,
),
);
}
// collect Share strings
final sharesCollected =
controllers.map((e) => e.text).toList();
final List<String> 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<void>(
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,
);
}
}

View file

@ -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<FrostCreateSignConfigView> createState() =>
_FrostCreateSignConfigViewState();
}
class _FrostCreateSignConfigViewState
extends ConsumerState<FrostCreateSignConfigView> {
bool _attemptSignLock = false;
Future<void> _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<StackColors>()!.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<StackColors>()!.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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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,
),
],
),
),
);
}
}

View file

@ -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<FrostImportSignConfigView> createState() =>
_FrostImportSignConfigViewState();
}
class _FrostImportSignConfigViewState
extends ConsumerState<FrostImportSignConfigView> {
late final TextEditingController configFieldController;
late final FocusNode configFocusNode;
bool _configEmpty = true;
bool _attemptSignLock = false;
Future<void> _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<void>(
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<StackColors>()!.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<StackColors>()!.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<void>.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();
},
),
],
),
),
);
}
}

View file

@ -14,9 +14,9 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.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/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/coin_control/coin_control_view.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/pages/send_view/frost_ms/recipient.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart'; import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/providers/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/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/fee_slider.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/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
@ -120,12 +121,29 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
); );
} }
final wallet =
ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet;
if (mounted && txData != null) { if (mounted && txData != null) {
ref.read(pFrostTxData.notifier).state = txData; 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( await Navigator.of(context).pushNamed(
FrostCreateSignConfigView.routeName, FrostStepScaffold.routeName,
arguments: widget.walletId,
); );
} }
} catch (e) { } catch (e) {

View file

@ -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<FrostSendStep1a> createState() => _FrostSendStep1aState();
}
class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
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<void> _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<StackColors>()!
.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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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();
},
),
],
),
);
}
}

View file

@ -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<FrostSendStep1b> createState() => _FrostSendStep1bState();
}
class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
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<void> _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<void>(
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();
},
),
],
),
);
}
}

View file

@ -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<FrostSendStep2> createState() => _FrostSendStep2State();
}
class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
final List<TextEditingController> controllers = [];
final List<FocusNode> focusNodes = [];
late final String myName;
late final List<String> participantsWithoutMe;
late final String myPreprocess;
late final int myIndex;
late final int threshold;
final List<bool> 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<StackColors>()!
.customTextButtonEnabledText,
),
),
],
),
),
),
],
),
],
),
),
SizedBox(
height: 220,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
QrImageView(
data: myPreprocess,
size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>.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<String> 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<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Failed to continue signing",
desktopPopRootNavigator: Util.isDesktop,
),
);
}
},
),
],
),
);
}
}

View file

@ -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<FrostSendStep3> createState() => _FrostSendStep3State();
}
class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
final List<TextEditingController> controllers = [];
final List<FocusNode> focusNodes = [];
late final String myName;
late final List<String> participantsWithoutMe;
late final List<String> participantsAll;
late final String myShare;
late final int myIndex;
final List<bool> 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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>.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<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Missing Shares",
desktopPopRootNavigator: Util.isDesktop,
),
);
}
// collect Share strings
final sharesCollected = controllers.map((e) => e.text).toList();
final List<String> 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<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Failed to complete signing process",
desktopPopRootNavigator: Util.isDesktop,
),
);
}
},
),
],
),
);
}
}

View file

@ -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<FrostSendStep4> createState() => _FrostSendStep4State();
}
class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
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<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.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<void>(
context: context,
builder: (_) => StackOkDialog(
title: "Broadcast error",
message: e.toString(),
desktopPopRootNavigator: Util.isDesktop,
),
);
} finally {
_broadcastLock = false;
}
},
),
],
),
);
}
}

View file

@ -10,8 +10,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; import 'package:stackwallet/frost_route_generator.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart'; import 'package:stackwallet/pages/settings_views/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/frost_participants_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/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/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/frost_scaffold.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class FrostMSWalletOptionsView extends ConsumerWidget { class FrostMSWalletOptionsView extends ConsumerWidget {

View file

@ -4,8 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:frostdart/frostdart.dart'; import 'package:frostdart/frostdart.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart'; import 'package:stackwallet/frost_route_generator.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/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/db/main_db_provider.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.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_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.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/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';

View file

@ -16,6 +16,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.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/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.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_claim_view.dart';
import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
import 'package:stackwallet/pages/receive_view/receive_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/frost_ms/frost_send_view.dart';
import 'package:stackwallet/pages/send_view/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'; 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_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.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/loading_indicator.dart';
import 'package:stackwallet/widgets/small_tor_icon.dart'; import 'package:stackwallet/widgets/small_tor_icon.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
@ -359,9 +360,24 @@ class _WalletViewState extends ConsumerState<WalletView> {
} }
Future<void> _onFrostSignPressed(BuildContext context) async { Future<void> _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( await Navigator.of(context).pushNamed(
FrostImportSignConfigView.routeName, FrostStepScaffold.routeName,
arguments: walletId,
); );
} }

View file

@ -10,7 +10,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/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/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'; 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/wallets/wallet/impl/bitcoin_frost_wallet.dart';
import 'package:stackwallet/widgets/custom_tab_view.dart'; import 'package:stackwallet/widgets/custom_tab_view.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/frost_scaffold.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
class MyWallet extends ConsumerStatefulWidget { class MyWallet extends ConsumerStatefulWidget {
const MyWallet({ const MyWallet({
Key? key, super.key,
required this.walletId, required this.walletId,
this.contractAddress, this.contractAddress,
}) : super(key: key); });
final String walletId; final String walletId;
final String? contractAddress; final String? contractAddress;
@ -85,10 +86,32 @@ class _MyWalletState extends ConsumerState<MyWallet> {
width: 200, width: 200,
buttonHeight: ButtonHeight.l, buttonHeight: ButtonHeight.l,
label: "Import sign config", label: "Import sign config",
onPressed: () { onPressed: () async {
Navigator.of(context).pushNamed( final wallet = ref
FrostImportSignConfigView.routeName, .read(pWallets)
arguments: widget.walletId, .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,
); );
}, },
), ),

View file

@ -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_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/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/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/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/new/select_new_frost_import_type_view.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_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/generate_receiving_uri_qr_code_view.dart';
import 'package:stackwallet/pages/receive_view/receive_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/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/frost_ms/frost_send_view.dart';
import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart';
import 'package:stackwallet/pages/send_view/token_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/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart';
import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/widgets/choose_coin_view.dart';
import 'package:stackwallet/widgets/frost_scaffold.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
/* /*
@ -574,48 +571,6 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); 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: // case MonkeyLoadedView.routeName:
// if (args is Tuple2<String, ChangeNotifierProvider<Manager>>) { // if (args is Tuple2<String, ChangeNotifierProvider<Manager>>) {
// return getRoute( // return getRoute(

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/themes/stack_colors.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';