diff --git a/assets/svg/swap2.svg b/assets/svg/swap2.svg
new file mode 100644
index 000000000..1c9ce8191
--- /dev/null
+++ b/assets/svg/swap2.svg
@@ -0,0 +1,10 @@
+
diff --git a/lib/electrumx_rpc/client_manager.dart b/lib/electrumx_rpc/client_manager.dart
index 8e580ec78..26db04b4b 100644
--- a/lib/electrumx_rpc/client_manager.dart
+++ b/lib/electrumx_rpc/client_manager.dart
@@ -51,12 +51,12 @@ class ClientManager {
if (_map[key] == null) {
throw Exception(
- "No managed ElectrumClient for $cryptoCurrency found.",
+ "No managed ElectrumClient for $key found.",
);
}
if (_heightCompleters[key] == null) {
throw Exception(
- "No managed _heightCompleters for $cryptoCurrency found.",
+ "No managed _heightCompleters for $key found.",
);
}
diff --git a/lib/frost_route_generator.dart b/lib/frost_route_generator.dart
new file mode 100644
index 000000000..a6905a243
--- /dev/null
+++ b/lib/frost_route_generator.dart
@@ -0,0 +1,274 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart';
+import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart';
+import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart';
+import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart';
+import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart';
+import 'package:stackwallet/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart';
+import 'package:stackwallet/route_generator.dart';
+import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
+
+typedef FrostStepRoute = ({String routeName, String title});
+
+enum FrostInterruptionDialogType {
+ walletCreation,
+ resharing,
+ transactionCreation;
+}
+
+final pFrostCreateCurrentStep = StateProvider.autoDispose((ref) => 1);
+final pFrostScaffoldCanPopDesktop = StateProvider.autoDispose((_) => false);
+final pFrostScaffoldArgs = StateProvider<
+ ({
+ ({String walletName, FrostCurrency frostCurrency}) info,
+ String? walletId,
+ List stepRoutes,
+ FrostInterruptionDialogType frostInterruptionDialogType,
+ NavigatorState parentNav,
+ })?>((ref) => null);
+
+abstract class FrostRouteGenerator {
+ static const bool useMaterialPageRoute = true;
+
+ static const List createNewConfigStepRoutes = [
+ (routeName: FrostCreateStep1a.routeName, title: FrostCreateStep1a.title),
+ (routeName: FrostCreateStep2.routeName, title: FrostCreateStep2.title),
+ (routeName: FrostCreateStep3.routeName, title: FrostCreateStep3.title),
+ (routeName: FrostCreateStep4.routeName, title: FrostCreateStep4.title),
+ (routeName: FrostCreateStep5.routeName, title: FrostCreateStep5.title),
+ ];
+
+ static const List importNewConfigStepRoutes = [
+ (routeName: FrostCreateStep1b.routeName, title: FrostCreateStep1b.title),
+ (routeName: FrostCreateStep2.routeName, title: FrostCreateStep2.title),
+ (routeName: FrostCreateStep3.routeName, title: FrostCreateStep3.title),
+ (routeName: FrostCreateStep4.routeName, title: FrostCreateStep4.title),
+ (routeName: FrostCreateStep5.routeName, title: FrostCreateStep5.title),
+ ];
+
+ static const List initiateReshareStepRoutes = [
+ (routeName: FrostReshareStep1a.routeName, title: FrostReshareStep1a.title),
+ (
+ routeName: FrostReshareStep2abd.routeName,
+ title: FrostReshareStep2abd.title
+ ),
+ (
+ routeName: FrostReshareStep3abd.routeName,
+ title: FrostReshareStep3abd.title
+ ),
+ (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title),
+ (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title),
+ ];
+
+ static const List importReshareStepRoutes = [
+ (routeName: FrostReshareStep1b.routeName, title: FrostReshareStep1b.title),
+ (
+ routeName: FrostReshareStep2abd.routeName,
+ title: FrostReshareStep2abd.title
+ ),
+ (
+ routeName: FrostReshareStep3abd.routeName,
+ title: FrostReshareStep3abd.title
+ ),
+ (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title),
+ (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title),
+ ];
+
+ static const List joinReshareStepRoutes = [
+ (routeName: FrostReshareStep1c.routeName, title: FrostReshareStep1c.title),
+ (routeName: FrostReshareStep2c.routeName, title: FrostReshareStep2c.title),
+ (routeName: FrostReshareStep3c.routeName, title: FrostReshareStep3c.title),
+ (routeName: FrostReshareStep4.routeName, title: FrostReshareStep4.title),
+ (routeName: FrostReshareStep5.routeName, title: FrostReshareStep5.title),
+ ];
+
+ static const List sendFrostTxStepRoutes = [
+ (routeName: FrostSendStep1a.routeName, title: FrostSendStep1a.title),
+ (routeName: FrostSendStep2.routeName, title: FrostSendStep2.title),
+ (routeName: FrostSendStep3.routeName, title: FrostSendStep3.title),
+ (routeName: FrostSendStep4.routeName, title: FrostSendStep4.title),
+ ];
+
+ static const List signFrostTxStepRoutes = [
+ (routeName: FrostSendStep1b.routeName, title: FrostSendStep1b.title),
+ (routeName: FrostSendStep2.routeName, title: FrostSendStep2.title),
+ (routeName: FrostSendStep3.routeName, title: FrostSendStep3.title),
+ (routeName: FrostSendStep4.routeName, title: FrostSendStep4.title),
+ ];
+
+ static Route generateRoute(RouteSettings settings) {
+ final args = settings.arguments;
+
+ switch (settings.name) {
+ case FrostCreateStep1a.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep1a(),
+ settings: settings,
+ );
+
+ case FrostCreateStep1b.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep1b(),
+ settings: settings,
+ );
+
+ case FrostCreateStep2.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep2(),
+ settings: settings,
+ );
+
+ case FrostCreateStep3.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep3(),
+ settings: settings,
+ );
+
+ case FrostCreateStep4.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep4(),
+ settings: settings,
+ );
+
+ case FrostCreateStep5.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostCreateStep5(),
+ settings: settings,
+ );
+
+ case FrostReshareStep1a.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep1a(),
+ settings: settings,
+ );
+
+ case FrostReshareStep1b.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep1b(),
+ settings: settings,
+ );
+
+ case FrostReshareStep1c.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep1c(),
+ settings: settings,
+ );
+
+ case FrostReshareStep2abd.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep2abd(),
+ settings: settings,
+ );
+
+ case FrostReshareStep2c.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep2c(),
+ settings: settings,
+ );
+
+ case FrostReshareStep3abd.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep3abd(),
+ settings: settings,
+ );
+
+ case FrostReshareStep3c.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep3c(),
+ settings: settings,
+ );
+
+ case FrostReshareStep4.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep4(),
+ settings: settings,
+ );
+
+ case FrostReshareStep5.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostReshareStep5(),
+ settings: settings,
+ );
+
+ case FrostSendStep1a.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostSendStep1a(),
+ settings: settings,
+ );
+
+ case FrostSendStep1b.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostSendStep1b(),
+ settings: settings,
+ );
+
+ case FrostSendStep2.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostSendStep2(),
+ settings: settings,
+ );
+
+ case FrostSendStep3.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostSendStep3(),
+ settings: settings,
+ );
+
+ case FrostSendStep4.routeName:
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => const FrostSendStep4(),
+ settings: settings,
+ );
+
+ default:
+ return _routeError("");
+ }
+ }
+
+ static Route _routeError(String message) {
+ return RouteGenerator.getRoute(
+ shouldUseMaterialRoute: useMaterialPageRoute,
+ builder: (_) => Placeholder(
+ child: Center(
+ child: Text(message),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart
index d84bfc708..6d0582c5c 100644
--- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart
+++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart
@@ -46,7 +46,7 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class AddWalletView extends ConsumerStatefulWidget {
- const AddWalletView({Key? key}) : super(key: key);
+ const AddWalletView({super.key});
static const routeName = "/addWallet";
@@ -134,11 +134,6 @@ class _AddWalletViewState extends ConsumerState {
_coins.remove(Coin.wownero);
}
- // Remove FROST from the list of coins based on our frostEnabled preference.
- if (!ref.read(prefsChangeNotifierProvider).frostEnabled) {
- _coins.remove(Coin.bitcoinFrost);
- }
-
// Remove Solana from the list of coins based on our frostEnabled preference.
if (!ref.read(prefsChangeNotifierProvider).solanaEnabled) {
_coins.remove(Coin.solana);
@@ -147,10 +142,6 @@ class _AddWalletViewState extends ConsumerState {
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {
- if (!ref.read(prefsChangeNotifierProvider).frostEnabled) {
- _coinsTestnet.remove(Coin.bitcoinFrostTestNet);
- }
-
coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/confirm_new_frost_ms_wallet_creation_view.dart b/lib/pages/add_wallet_views/frost_ms/new/confirm_new_frost_ms_wallet_creation_view.dart
deleted file mode 100644
index ffd1127b6..000000000
--- a/lib/pages/add_wallet_views/frost_ms/new/confirm_new_frost_ms_wallet_creation_view.dart
+++ /dev/null
@@ -1,336 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/notifications/show_flush_bar.dart';
-import 'package:stackwallet/pages/frost_mascot.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_desktop_specific/desktop_home_view.dart';
-import 'package:stackwallet/providers/db/main_db_provider.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/node_service_provider.dart';
-import 'package:stackwallet/providers/global/prefs_provider.dart';
-import 'package:stackwallet/providers/global/secure_store_provider.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/assets.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.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/wallets/wallet/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_interruption_dialog.dart';
-import 'package:stackwallet/widgets/loading_indicator.dart';
-
-import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
-
-class ConfirmNewFrostMSWalletCreationView extends ConsumerStatefulWidget {
- const ConfirmNewFrostMSWalletCreationView({
- super.key,
- required this.walletName,
- required this.coin,
- });
-
- static const String routeName = "/confirmNewFrostMSWalletCreationView";
-
- final String walletName;
- final Coin coin;
-
- @override
- ConsumerState createState() =>
- _ConfirmNewFrostMSWalletCreationViewState();
-}
-
-class _ConfirmNewFrostMSWalletCreationViewState
- extends ConsumerState {
- late final String seed, recoveryString, serializedKeys, multisigConfig;
- late final Uint8List multisigId;
-
- @override
- void initState() {
- seed = ref.read(pFrostStartKeyGenData.state).state!.seed;
- serializedKeys =
- ref.read(pFrostCompletedKeyGenData.state).state!.serializedKeys;
- recoveryString =
- ref.read(pFrostCompletedKeyGenData.state).state!.recoveryString;
- multisigId = ref.read(pFrostCompletedKeyGenData.state).state!.multisigId;
- multisigConfig = ref.read(pFrostMultisigConfig.state).state!;
-
- super.initState();
- }
-
- @override
- Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async {
- await showDialog(
- context: context,
- builder: (_) => FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName:
- Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName,
- ),
- );
-
- return false;
- },
- child: ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: DesktopHomeView.routeName,
- ),
- );
- },
- ),
- trailing: FrostMascot(
- title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: HomeView.routeName,
- ),
- );
- },
- ),
- title: Text(
- "Finalize FROST multisig wallet",
- 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: [
- Text(
- "Ensure your multisig ID matches that of each other participant",
- style: STextStyles.pageTitleH2(context),
- ),
- const _Div(),
- DetailItem(
- title: "ID",
- detail: multisigId.toString(),
- button: Util.isDesktop
- ? IconCopyButton(
- data: multisigId.toString(),
- )
- : SimpleCopyButton(
- data: multisigId.toString(),
- ),
- ),
- const _Div(),
- const _Div(),
- Text(
- "Back up your keys and config",
- style: STextStyles.pageTitleH2(context),
- ),
- const _Div(),
- DetailItem(
- title: "Multisig Config",
- detail: multisigConfig,
- button: Util.isDesktop
- ? IconCopyButton(
- data: multisigConfig,
- )
- : SimpleCopyButton(
- data: multisigConfig,
- ),
- ),
- const _Div(),
- DetailItem(
- title: "Keys",
- detail: serializedKeys,
- button: Util.isDesktop
- ? IconCopyButton(
- data: serializedKeys,
- )
- : SimpleCopyButton(
- data: serializedKeys,
- ),
- ),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Confirm",
- onPressed: () async {
- bool progressPopped = false;
- try {
- unawaited(
- showDialog(
- context: context,
- barrierDismissible: false,
- useSafeArea: true,
- builder: (ctx) {
- return const Center(
- child: LoadingIndicator(
- width: 50,
- height: 50,
- ),
- );
- },
- ),
- );
-
- final info = WalletInfo.createNew(
- coin: widget.coin,
- name: widget.walletName,
- );
-
- final wallet = await Wallet.create(
- walletInfo: info,
- mainDB: ref.read(mainDBProvider),
- secureStorageInterface: ref.read(secureStoreProvider),
- nodeService: ref.read(nodeServiceChangeNotifierProvider),
- prefs: ref.read(prefsChangeNotifierProvider),
- );
-
- await (wallet as BitcoinFrostWallet).initializeNewFrost(
- mnemonic: seed,
- multisigConfig: multisigConfig,
- recoveryString: recoveryString,
- serializedKeys: serializedKeys,
- multisigId: multisigId,
- myName: ref.read(pFrostMyName.state).state!,
- participants: Frost.getParticipants(
- multisigConfig:
- ref.read(pFrostMultisigConfig.state).state!,
- ),
- threshold: Frost.getThreshold(
- multisigConfig:
- ref.read(pFrostMultisigConfig.state).state!,
- ),
- );
-
- await info.setMnemonicVerified(
- isar: ref.read(mainDBProvider).isar,
- );
-
- ref.read(pWallets).addWallet(wallet);
-
- // pop progress dialog
- if (mounted) {
- Navigator.pop(context);
- progressPopped = true;
- }
-
- if (mounted) {
- if (Util.isDesktop) {
- Navigator.of(context).popUntil(
- ModalRoute.withName(
- DesktopHomeView.routeName,
- ),
- );
- } else {
- unawaited(
- Navigator.of(context).pushNamedAndRemoveUntil(
- HomeView.routeName,
- (route) => false,
- ),
- );
- }
-
- ref.read(pFrostMultisigConfig.state).state = null;
- ref.read(pFrostStartKeyGenData.state).state = null;
- ref.read(pFrostSecretSharesData.state).state = null;
-
- unawaited(
- showFloatingFlushBar(
- type: FlushBarType.success,
- message: "Your wallet is set up.",
- iconAsset: Assets.svg.check,
- context: context,
- ),
- );
- }
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- // pop progress dialog
- if (mounted && !progressPopped) {
- Navigator.pop(context);
- progressPopped = true;
- }
- // TODO: handle gracefully
- rethrow;
- }
- },
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart b/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart
index 9f42a3fa6..620d9df06 100644
--- a/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart
+++ b/lib/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart
@@ -1,33 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/pages/frost_mascot.dart';
-import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/share_new_multisig_config_view.dart';
+import 'package:stackwallet/frost_route_generator.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/services/frost.dart';
import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
import 'package:stackwallet/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/blue_text_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
+import 'package:stackwallet/widgets/frost_mascot.dart';
+import 'package:stackwallet/widgets/frost_scaffold.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class CreateNewFrostMsWalletView extends ConsumerStatefulWidget {
const CreateNewFrostMsWalletView({
super.key,
required this.walletName,
- required this.coin,
+ required this.frostCurrency,
});
static const String routeName = "/createNewFrostMsWalletView";
final String walletName;
- final Coin coin;
+ final FrostCurrency frostCurrency;
@override
ConsumerState createState() =>
@@ -67,13 +71,14 @@ class _NewFrostMsWalletViewState
}
final hasEmptyParticipants = controllers
- .map((e) => e.text.isEmpty)
+ .map((e) => e.text.trim().isEmpty)
.reduce((value, element) => value |= element);
if (hasEmptyParticipants) {
return "Participants must not be empty";
}
- if (controllers.length != controllers.map((e) => e.text).toSet().length) {
+ if (controllers.length !=
+ controllers.map((e) => e.text.trim()).toSet().length) {
return "Duplicate participant name found";
}
@@ -102,6 +107,31 @@ class _NewFrostMsWalletViewState
}
}
+ void _showWhatIsThresholdDialog() {
+ showDialog(
+ context: context,
+ builder: (_) => SimpleMobileDialog(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // TODO: [prio=high] need text from designers!
+ Text(
+ "What is a threshold?",
+ style: STextStyles.w600_20(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Text(
+ "Text here",
+ style: STextStyles.w400_16(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
@override
void dispose() {
_thresholdController.dispose();
@@ -118,12 +148,14 @@ class _NewFrostMsWalletViewState
condition: Util.isDesktop,
builder: (child) => DesktopScaffold(
background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
+ appBar: const DesktopAppBar(
isCompactHeight: false,
leading: AppBarBackButton(),
+ // TODO: [prio=high] get rid of placeholder text??
trailing: FrostMascot(
title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
+ body:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
),
),
body: SizedBox(
@@ -144,7 +176,7 @@ class _NewFrostMsWalletViewState
},
),
title: Text(
- "New FROST multisig config",
+ "Create new group",
style: STextStyles.navBarTitle(context),
),
),
@@ -172,9 +204,21 @@ class _NewFrostMsWalletViewState
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- "Threshold",
- style: STextStyles.label(context),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ "Threshold",
+ style: STextStyles.w500_14(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ CustomTextButton(
+ text: "What is a threshold?",
+ onTap: _showWhatIsThresholdDialog,
+ ),
+ ],
),
const SizedBox(
height: 10,
@@ -183,22 +227,53 @@ class _NewFrostMsWalletViewState
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
controller: _thresholdController,
+ decoration: InputDecoration(
+ hintText: "Enter number of signatures",
+ hintStyle: STextStyles.fieldLabel(context),
+ ),
),
const SizedBox(
height: 16,
),
Text(
"Number of participants",
- style: STextStyles.label(context),
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
),
const SizedBox(
height: 10,
),
- TextField(
- keyboardType: TextInputType.number,
- inputFormatters: [FilteringTextInputFormatter.digitsOnly],
- controller: _participantsController,
- onChanged: _participantsCountChanged,
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ TextField(
+ keyboardType: TextInputType.number,
+ inputFormatters: [FilteringTextInputFormatter.digitsOnly],
+ controller: _participantsController,
+ onChanged: _participantsCountChanged,
+ decoration: InputDecoration(
+ hintText: "Enter number of participants",
+ hintStyle: STextStyles.fieldLabel(context),
+ ),
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: RoundedWhiteContainer(
+ child: Text(
+ "Enter number of signatures required for fund management",
+ style: STextStyles.label(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
),
const SizedBox(
height: 16,
@@ -206,24 +281,75 @@ class _NewFrostMsWalletViewState
if (controllers.isNotEmpty)
Text(
"My name",
- style: STextStyles.label(context),
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
),
if (controllers.isNotEmpty)
const SizedBox(
height: 10,
),
if (controllers.isNotEmpty)
- TextField(
- controller: controllers.first,
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ TextField(
+ controller: controllers.first,
+ decoration: InputDecoration(
+ hintText: "Enter your name",
+ hintStyle: STextStyles.fieldLabel(context),
+ ),
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: RoundedWhiteContainer(
+ child: Text(
+ "Type your name in one word without spaces",
+ style: STextStyles.label(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
),
if (controllers.length > 1)
const SizedBox(
height: 16,
),
if (controllers.length > 1)
- Text(
- "Remaining participants",
- style: STextStyles.label(context),
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "Remaining participants",
+ style: STextStyles.w500_14(context).copyWith(
+ color:
+ Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: RoundedWhiteContainer(
+ child: Text(
+ "Type each name in one word without spaces",
+ style: STextStyles.label(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
),
if (controllers.length > 1)
Column(
@@ -235,6 +361,10 @@ class _NewFrostMsWalletViewState
),
child: TextField(
controller: controllers[i],
+ decoration: InputDecoration(
+ hintText: "Enter name",
+ hintStyle: STextStyles.fieldLabel(context),
+ ),
),
),
],
@@ -244,7 +374,7 @@ class _NewFrostMsWalletViewState
height: 16,
),
PrimaryButton(
- label: "Generate",
+ label: "Create new group",
onPressed: () async {
if (FocusScope.of(context).hasFocus) {
FocusScope.of(context).unfocus();
@@ -263,20 +393,29 @@ class _NewFrostMsWalletViewState
}
final config = Frost.createMultisigConfig(
- name: controllers.first.text,
+ name: controllers.first.text.trim(),
threshold: int.parse(_thresholdController.text),
- participants: controllers.map((e) => e.text).toList(),
+ participants: controllers.map((e) => e.text.trim()).toList(),
);
- ref.read(pFrostMyName.notifier).state = controllers.first.text;
+ ref.read(pFrostMyName.notifier).state =
+ controllers.first.text.trim();
ref.read(pFrostMultisigConfig.notifier).state = config;
- await Navigator.of(context).pushNamed(
- ShareNewMultisigConfigView.routeName,
- arguments: (
+ ref.read(pFrostScaffoldArgs.state).state = (
+ info: (
walletName: widget.walletName,
- coin: widget.coin,
+ frostCurrency: widget.frostCurrency,
),
+ walletId: null,
+ stepRoutes: FrostRouteGenerator.createNewConfigStepRoutes,
+ frostInterruptionDialogType:
+ FrostInterruptionDialogType.walletCreation,
+ parentNav: Navigator.of(context),
+ );
+
+ await Navigator.of(context).pushNamed(
+ FrostStepScaffold.routeName,
);
},
),
diff --git a/lib/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart b/lib/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart
deleted file mode 100644
index 1234dbf8a..000000000
--- a/lib/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart
+++ /dev/null
@@ -1,436 +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/frost_mascot.dart';
-import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_shares_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_desktop_specific/desktop_home_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-import 'package:stackwallet/widgets/dialogs/frost_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 FrostShareCommitmentsView extends ConsumerStatefulWidget {
- const FrostShareCommitmentsView({
- super.key,
- required this.walletName,
- required this.coin,
- });
-
- static const String routeName = "/frostShareCommitmentsView";
-
- final String walletName;
- final Coin coin;
-
- @override
- ConsumerState createState() =>
- _FrostShareCommitmentsViewState();
-}
-
-class _FrostShareCommitmentsViewState
- extends ConsumerState {
- final List controllers = [];
- final List focusNodes = [];
-
- late final List participants;
- late final String myCommitment;
- late final int myIndex;
-
- final List fieldIsEmptyFlags = [];
-
- @override
- void initState() {
- participants = Frost.getParticipants(
- multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
- );
- myIndex = participants.indexOf(ref.read(pFrostMyName.state).state!);
- myCommitment = ref.read(pFrostStartKeyGenData.state).state!.commitments;
-
- // temporarily remove my name
- participants.removeAt(myIndex);
-
- for (int i = 0; i < participants.length; i++) {
- controllers.add(TextEditingController());
- focusNodes.add(FocusNode());
- fieldIsEmptyFlags.add(true);
- }
- super.initState();
- }
-
- @override
- void dispose() {
- for (int i = 0; i < controllers.length; i++) {
- controllers[i].dispose();
- }
- for (int i = 0; i < focusNodes.length; i++) {
- focusNodes[i].dispose();
- }
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async {
- await showDialog(
- context: context,
- builder: (_) => FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName:
- Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName,
- ),
- );
- return false;
- },
- child: ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: DesktopHomeView.routeName,
- ),
- );
- },
- ),
- trailing: FrostMascot(
- title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: HomeView.routeName,
- ),
- );
- },
- ),
- title: Text(
- "Commitments",
- 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: myCommitment,
- size: 220,
- backgroundColor: Theme.of(context)
- .extension()!
- .background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const _Div(),
- DetailItem(
- title: "My name",
- detail: ref.watch(pFrostMyName.state).state!,
- ),
- const _Div(),
- DetailItem(
- title: "My commitment",
- detail: myCommitment,
- button: Util.isDesktop
- ? IconCopyButton(
- data: myCommitment,
- )
- : SimpleCopyButton(
- data: myCommitment,
- ),
- ),
- const _Div(),
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- for (int i = 0; i < participants.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("frostCommitmentsTextFieldKey_$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 ${participants[i]}'s commitment",
- 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 Commitment Field Input.",
- key: Key(
- "frostCommitmentsClearButtonKey_$i"),
- onTap: () {
- controllers[i].text = "";
-
- setState(() {
- fieldIsEmptyFlags[i] = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Commitment Field Input.",
- key: Key(
- "frostCommitmentsPasteButtonKey_$i"),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- controllers[i].text =
- data.text!.trim();
- }
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i]
- .text
- .isEmpty;
- });
- },
- child: fieldIsEmptyFlags[i]
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (fieldIsEmptyFlags[i])
- TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera For Scanning QR Code.",
- key: Key(
- "frostCommitmentsScanQrButtonKey_$i"),
- onTap: () async {
- try {
- if (FocusScope.of(context)
- .hasFocus) {
- FocusScope.of(context)
- .unfocus();
- await Future.delayed(
- const Duration(
- milliseconds: 75));
- }
-
- final qrResult =
- await BarcodeScanner.scan();
-
- controllers[i].text =
- qrResult.rawContent;
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i]
- .text
- .isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Generate shares",
- enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e),
- onPressed: () async {
- // check for empty commitments
- if (controllers
- .map((e) => e.text.isEmpty)
- .reduce((value, element) => value |= element)) {
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Missing commitments",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
-
- // collect commitment strings and insert my own at the correct index
- final commitments = controllers.map((e) => e.text).toList();
- commitments.insert(myIndex, myCommitment);
-
- try {
- ref.read(pFrostSecretSharesData.notifier).state =
- Frost.generateSecretShares(
- multisigConfigWithNamePtr: ref
- .read(pFrostStartKeyGenData.state)
- .state!
- .multisigConfigWithNamePtr,
- mySeed: ref.read(pFrostStartKeyGenData.state).state!.seed,
- secretShareMachineWrapperPtr: ref
- .read(pFrostStartKeyGenData.state)
- .state!
- .secretShareMachineWrapperPtr,
- commitments: commitments,
- );
-
- await Navigator.of(context).pushNamed(
- FrostShareSharesView.routeName,
- arguments: (
- walletName: widget.walletName,
- coin: widget.coin,
- ),
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Failed to generate shares",
- message: e.toString(),
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
- },
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/frost_share_shares_view.dart b/lib/pages/add_wallet_views/frost_ms/new/frost_share_shares_view.dart
deleted file mode 100644
index 20ac39c03..000000000
--- a/lib/pages/add_wallet_views/frost_ms/new/frost_share_shares_view.dart
+++ /dev/null
@@ -1,402 +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/frost_mascot.dart';
-import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/confirm_new_frost_ms_wallet_creation_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_desktop_specific/desktop_home_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-import 'package:stackwallet/widgets/dialogs/frost_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 FrostShareSharesView extends ConsumerStatefulWidget {
- const FrostShareSharesView({
- super.key,
- required this.walletName,
- required this.coin,
- });
-
- static const String routeName = "/frostShareSharesView";
-
- final String walletName;
- final Coin coin;
-
- @override
- ConsumerState createState() =>
- _FrostShareSharesViewState();
-}
-
-class _FrostShareSharesViewState extends ConsumerState {
- final List controllers = [];
- final List focusNodes = [];
-
- late final List participants;
- late final String myShare;
- late final int myIndex;
-
- final List fieldIsEmptyFlags = [];
-
- @override
- void initState() {
- participants = Frost.getParticipants(
- multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
- );
- myIndex = participants.indexOf(ref.read(pFrostMyName.state).state!);
- myShare = ref.read(pFrostSecretSharesData.state).state!.share;
-
- // temporarily remove my name. Added back later
- participants.removeAt(myIndex);
-
- for (int i = 0; i < participants.length; i++) {
- controllers.add(TextEditingController());
- focusNodes.add(FocusNode());
- fieldIsEmptyFlags.add(true);
- }
- super.initState();
- }
-
- @override
- void dispose() {
- for (int i = 0; i < controllers.length; i++) {
- controllers[i].dispose();
- }
- for (int i = 0; i < focusNodes.length; i++) {
- focusNodes[i].dispose();
- }
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async {
- await showDialog(
- context: context,
- builder: (_) => FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName:
- Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName,
- ),
- );
- return false;
- },
- child: ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: DesktopHomeView.routeName,
- ),
- );
- },
- ),
- trailing: FrostMascot(
- title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.walletCreation,
- popUntilOnYesRouteName: HomeView.routeName,
- ),
- );
- },
- ),
- title: Text(
- "Generate shares",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- height: 220,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data: myShare,
- size: 220,
- backgroundColor: Theme.of(context)
- .extension()!
- .background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const _Div(),
- DetailItem(
- title: "My name",
- detail: ref.watch(pFrostMyName.state).state!,
- ),
- const _Div(),
- DetailItem(
- title: "My share",
- detail: myShare,
- button: Util.isDesktop
- ? IconCopyButton(
- data: myShare,
- )
- : SimpleCopyButton(
- data: myShare,
- ),
- ),
- const _Div(),
- for (int i = 0; i < participants.length; i++)
- Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: Key("frSharesTextFieldKey_$i"),
- controller: controllers[i],
- focusNode: focusNodes[i],
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "Enter ${participants[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("frSharesClearButtonKey_$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("frSharesPasteButtonKey_$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("frSharesScanQrButtonKey_$i"),
- onTap: () async {
- try {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(
- const Duration(milliseconds: 75));
- }
-
- final qrResult =
- await BarcodeScanner.scan();
-
- controllers[i].text =
- qrResult.rawContent;
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i].text.isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Generate",
- onPressed: () async {
- // check for empty commitments
- if (controllers
- .map((e) => e.text.isEmpty)
- .reduce((value, element) => value |= element)) {
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Missing shares",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
-
- // collect commitment strings and insert my own at the correct index
- final shares = controllers.map((e) => e.text).toList();
- shares.insert(myIndex, myShare);
-
- try {
- ref.read(pFrostCompletedKeyGenData.notifier).state =
- Frost.completeKeyGeneration(
- multisigConfigWithNamePtr: ref
- .read(pFrostStartKeyGenData.state)
- .state!
- .multisigConfigWithNamePtr,
- secretSharesResPtr: ref
- .read(pFrostSecretSharesData.state)
- .state!
- .secretSharesResPtr,
- shares: shares,
- );
- await Navigator.of(context).pushNamed(
- ConfirmNewFrostMSWalletCreationView.routeName,
- arguments: (
- walletName: widget.walletName,
- coin: widget.coin,
- ),
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Failed to complete key generation",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
- },
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/import_new_frost_ms_wallet_view.dart b/lib/pages/add_wallet_views/frost_ms/new/import_new_frost_ms_wallet_view.dart
deleted file mode 100644
index 4eeb3a045..000000000
--- a/lib/pages/add_wallet_views/frost_ms/new/import_new_frost_ms_wallet_view.dart
+++ /dev/null
@@ -1,390 +0,0 @@
-import 'package:barcode_scan2/barcode_scan2.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/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';
-
-import 'package:stackwallet/pages/frost_mascot.dart';
-
-class ImportNewFrostMsWalletView extends ConsumerStatefulWidget {
- const ImportNewFrostMsWalletView({
- super.key,
- required this.walletName,
- required this.coin,
- });
-
- static const String routeName = "/importNewFrostMsWalletView";
-
- final String walletName;
- final Coin coin;
-
- @override
- ConsumerState createState() =>
- _ImportNewFrostMsWalletViewState();
-}
-
-class _ImportNewFrostMsWalletViewState
- extends ConsumerState {
- late final TextEditingController myNameFieldController, configFieldController;
- late final FocusNode myNameFocusNode, configFocusNode;
-
- bool _nameEmpty = true, _configEmpty = true;
-
- @override
- void initState() {
- myNameFieldController = TextEditingController();
- configFieldController = TextEditingController();
- myNameFocusNode = FocusNode();
- configFocusNode = FocusNode();
- super.initState();
- }
-
- @override
- void dispose() {
- myNameFieldController.dispose();
- configFieldController.dispose();
- myNameFocusNode.dispose();
- configFocusNode.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- trailing: FrostMascot(
- title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Import FROST multisig config",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(
- height: 16,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("frMyNameTextFieldKey"),
- controller: myNameFieldController,
- onChanged: (_) {
- setState(() {
- _nameEmpty = myNameFieldController.text.isEmpty;
- });
- },
- focusNode: myNameFocusNode,
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "My name",
- myNameFocusNode,
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
- ),
- suffixIcon: Padding(
- padding: _nameEmpty
- ? const EdgeInsets.only(right: 8)
- : const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- !_nameEmpty
- ? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears The Config Field.",
- key: const Key("frMyNameClearButtonKey"),
- onTap: () {
- myNameFieldController.text = "";
-
- setState(() {
- _nameEmpty = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Name Field.",
- key: const Key("frMyNamePasteButtonKey"),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- myNameFieldController.text =
- data.text!.trim();
- }
-
- setState(() {
- _nameEmpty =
- myNameFieldController.text.isEmpty;
- });
- },
- child: _nameEmpty
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 16,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("frConfigTextFieldKey"),
- controller: configFieldController,
- onChanged: (_) {
- setState(() {
- _configEmpty = configFieldController.text.isEmpty;
- });
- },
- focusNode: configFocusNode,
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "Enter config",
- configFocusNode,
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
- ),
- suffixIcon: Padding(
- padding: _configEmpty
- ? const EdgeInsets.only(right: 8)
- : const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- !_configEmpty
- ? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears The Config Field.",
- key: const Key("frConfigClearButtonKey"),
- onTap: () {
- configFieldController.text = "";
-
- setState(() {
- _configEmpty = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Config Field Input.",
- key: const Key("frConfigPasteButtonKey"),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- configFieldController.text =
- data.text!.trim();
- }
-
- setState(() {
- _configEmpty =
- configFieldController.text.isEmpty;
- });
- },
- child: _configEmpty
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (_configEmpty)
- TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera For Scanning QR Code.",
- key: const Key("frConfigScanQrButtonKey"),
- onTap: () async {
- try {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(
- const Duration(milliseconds: 75));
- }
-
- final qrResult = await BarcodeScanner.scan();
-
- configFieldController.text =
- qrResult.rawContent;
-
- setState(() {
- _configEmpty =
- configFieldController.text.isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 16,
- ),
- if (!Util.isDesktop) const Spacer(),
- const SizedBox(
- height: 16,
- ),
- PrimaryButton(
- label: "Start key generation",
- enabled: !_nameEmpty && !_configEmpty,
- onPressed: () async {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- }
-
- final config = configFieldController.text;
-
- if (!Frost.validateEncodedMultisigConfig(
- encodedConfig: config)) {
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Invalid config",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
-
- if (!Frost.getParticipants(multisigConfig: config)
- .contains(myNameFieldController.text)) {
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "My name not found in config participants",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
-
- ref.read(pFrostMyName.state).state = myNameFieldController.text;
- ref.read(pFrostMultisigConfig.notifier).state = config;
-
- ref.read(pFrostStartKeyGenData.state).state =
- Frost.startKeyGeneration(
- multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
- myName: ref.read(pFrostMyName.state).state!,
- );
-
- await Navigator.of(context).pushNamed(
- FrostShareCommitmentsView.routeName,
- arguments: (
- walletName: widget.walletName,
- coin: widget.coin,
- ),
- );
- },
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart b/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart
new file mode 100644
index 000000000..5596534a3
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart
@@ -0,0 +1,360 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
+import 'package:stackwallet/widgets/background.dart';
+import 'package:stackwallet/widgets/conditional_parent.dart';
+import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
+import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
+import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
+import 'package:stackwallet/widgets/frost_scaffold.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+
+class SelectNewFrostImportTypeView extends ConsumerStatefulWidget {
+ const SelectNewFrostImportTypeView({
+ super.key,
+ required this.walletName,
+ required this.frostCurrency,
+ });
+
+ static const String routeName = "/selectNewFrostImportTypeView";
+
+ final String walletName;
+ final FrostCurrency frostCurrency;
+
+ @override
+ ConsumerState createState() =>
+ _SelectNewFrostImportTypeViewState();
+}
+
+class _SelectNewFrostImportTypeViewState
+ extends ConsumerState {
+ _ImportOption _selectedOption = _ImportOption.multisigNew;
+
+ @override
+ Widget build(BuildContext context) {
+ return ConditionalParent(
+ condition: Util.isDesktop,
+ builder: (content) => DesktopScaffold(
+ appBar: const DesktopAppBar(
+ leading: AppBarBackButton(),
+ trailing: ExitToMyStackButton(),
+ isCompactHeight: false,
+ ),
+ body: SizedBox(
+ width: 480,
+ child: content,
+ ),
+ ),
+ child: ConditionalParent(
+ condition: !Util.isDesktop,
+ builder: (content) => Background(
+ child: Scaffold(
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ appBar: AppBar(
+ leading: AppBarBackButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ actions: [
+ AspectRatio(
+ aspectRatio: 1,
+ child: AppBarIconButton(
+ size: 36,
+ icon: SvgPicture.asset(
+ Assets.svg.circleQuestion,
+ width: 20,
+ height: 20,
+ colorFilter: ColorFilter.mode(
+ Theme.of(context)
+ .extension()!
+ .topNavIconPrimary,
+ BlendMode.srcIn,
+ ),
+ ),
+ onPressed: () async {
+ await showDialog(
+ context: context,
+ builder: (_) => const _FrostJoinInfoDialog(),
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ body: Container(
+ color: Theme.of(context).extension()!.background,
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: LayoutBuilder(
+ builder: (ctx, constraints) {
+ return SingleChildScrollView(
+ child: ConstrainedBox(
+ constraints:
+ BoxConstraints(minHeight: constraints.maxHeight),
+ child: IntrinsicHeight(
+ child: content,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ ),
+ child: Column(
+ children: [
+ ..._ImportOption.values.map(
+ (e) => Padding(
+ padding: const EdgeInsets.only(bottom: 16),
+ child: _ImportOptionCard(
+ onPressed: () => setState(() => _selectedOption = e),
+ title: e.info,
+ description: e.description,
+ value: e,
+ groupValue: _selectedOption,
+ ),
+ ),
+ ),
+ const Spacer(),
+ PrimaryButton(
+ label: "Continue",
+ onPressed: () async {
+ final String route;
+ switch (_selectedOption) {
+ case _ImportOption.multisigNew:
+ ref.read(pFrostScaffoldArgs.state).state = (
+ info: (
+ walletName: widget.walletName,
+ frostCurrency: widget.frostCurrency,
+ ),
+ walletId: null, // no wallet id yet
+ stepRoutes: FrostRouteGenerator.importNewConfigStepRoutes,
+ parentNav: Navigator.of(context),
+ frostInterruptionDialogType:
+ FrostInterruptionDialogType.walletCreation,
+ );
+ break;
+
+ case _ImportOption.resharerExisting:
+ ref.read(pFrostScaffoldArgs.state).state = (
+ info: (
+ walletName: widget.walletName,
+ frostCurrency: widget.frostCurrency,
+ ),
+ walletId: null, // no wallet id yet
+ stepRoutes: FrostRouteGenerator.joinReshareStepRoutes,
+ parentNav: Navigator.of(context),
+ frostInterruptionDialogType:
+ FrostInterruptionDialogType.resharing,
+ );
+ break;
+ }
+
+ await Navigator.of(context).pushNamed(
+ FrostStepScaffold.routeName,
+ );
+ },
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+enum _ImportOption {
+ multisigNew,
+ resharerExisting;
+
+ String get info {
+ switch (this) {
+ case _ImportOption.multisigNew:
+ return "I want to join a new group";
+ case _ImportOption.resharerExisting:
+ return "I want to join an existing group";
+ }
+ }
+
+ String get description {
+ switch (this) {
+ case _ImportOption.multisigNew:
+ return "You are currently participating in the process of creating a new group";
+ case _ImportOption.resharerExisting:
+ return "You are joining an existing group through the process of resharing";
+ }
+ }
+}
+
+class _ImportOptionCard extends StatefulWidget {
+ const _ImportOptionCard({
+ super.key,
+ required this.onPressed,
+ required this.title,
+ required this.description,
+ required this.value,
+ required this.groupValue,
+ });
+
+ final VoidCallback onPressed;
+ final String title;
+ final String description;
+ final _ImportOption value;
+ final _ImportOption groupValue;
+
+ @override
+ State<_ImportOptionCard> createState() => _ImportOptionCardState();
+}
+
+class _ImportOptionCardState extends State<_ImportOptionCard> {
+ @override
+ Widget build(BuildContext context) {
+ return RoundedWhiteContainer(
+ padding: const EdgeInsets.all(0),
+ onPressed: widget.onPressed,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(6.0),
+ child: Radio(
+ value: widget.value,
+ groupValue: widget.groupValue,
+ activeColor: Theme.of(context)
+ .extension()!
+ .radioButtonIconEnabled,
+ onChanged: (_) => widget.onPressed(),
+ ),
+ ),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.only(
+ top: 12.0,
+ right: 12.0,
+ bottom: 12.0,
+ ),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ widget.title,
+ style: STextStyles.w600_16(context),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 2,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ widget.description,
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .textSubtitle1,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class _FrostJoinInfoDialog extends StatelessWidget {
+ const _FrostJoinInfoDialog({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SimpleMobileDialog(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // TODO: [prio=high] need text from designers!
+ Text(
+ "Join a group",
+ style: STextStyles.w600_20(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Text(
+ "Text here",
+ style: STextStyles.w400_16(context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Text(
+ "What is resharing?",
+ style: STextStyles.w600_16(context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Text(
+ "In cryptocurrency, you are your own bank."
+ " Imagine keeping cash at home. If that cash"
+ " burns down or gets stolen, you lose it and"
+ " nobody will help you get your money back.",
+ style: STextStyles.w400_16(context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Text(
+ "Since cryptocurrency is digital money, your "
+ "wallet key is like that “cash” you keep at "
+ "home. If you lose your phone or if you "
+ "forget your wallet PIN, but you have your "
+ "wallet key, your crypto money will be safe. "
+ "That is why you should keep your wallet key "
+ "safe.",
+ style: STextStyles.w400_16(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Text(
+ "Why write it down?",
+ style: STextStyles.w600_16(context),
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ Text(
+ "You do not put your cash on display, do you?"
+ " Keeping your wallet key on a digital device"
+ " is like having it on display for thieves - "
+ "malicious software and hackers. Write your "
+ "wallet key down on paper in multiple copies "
+ "and keep them in a real, physical safe.",
+ style: STextStyles.w400_16(context),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/share_new_multisig_config_view.dart b/lib/pages/add_wallet_views/frost_ms/new/share_new_multisig_config_view.dart
deleted file mode 100644
index 7d463c4ca..000000000
--- a/lib/pages/add_wallet_views/frost_ms/new/share_new_multisig_config_view.dart
+++ /dev/null
@@ -1,165 +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/frost_mascot.dart';
-import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_commitments_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/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.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';
-
-class ShareNewMultisigConfigView extends ConsumerStatefulWidget {
- const ShareNewMultisigConfigView({
- super.key,
- required this.walletName,
- required this.coin,
- });
-
- static const String routeName = "/shareNewMultisigConfigView";
-
- final String walletName;
- final Coin coin;
-
- @override
- ConsumerState createState() =>
- _ShareNewMultisigConfigViewState();
-}
-
-class _ShareNewMultisigConfigViewState
- extends ConsumerState {
- @override
- Widget build(BuildContext context) {
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- trailing: FrostMascot(
- title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Multisig 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(
- children: [
- if (!Util.isDesktop) const Spacer(),
- SizedBox(
- height: 220,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data:
- ref.watch(pFrostMultisigConfig.state).state ?? "Error",
- size: 220,
- backgroundColor:
- Theme.of(context).extension()!.background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const SizedBox(
- height: 32,
- ),
- DetailItem(
- title: "Encoded config",
- detail: ref.watch(pFrostMultisigConfig.state).state ?? "Error",
- button: Util.isDesktop
- ? IconCopyButton(
- data: ref.watch(pFrostMultisigConfig.state).state ??
- "Error",
- )
- : SimpleCopyButton(
- data: ref.watch(pFrostMultisigConfig.state).state ??
- "Error",
- ),
- ),
- SizedBox(
- height: Util.isDesktop ? 64 : 16,
- ),
- if (!Util.isDesktop)
- const Spacer(
- flex: 2,
- ),
- PrimaryButton(
- label: "Start key generation",
- onPressed: () async {
- ref.read(pFrostStartKeyGenData.notifier).state =
- Frost.startKeyGeneration(
- multisigConfig: ref.watch(pFrostMultisigConfig.state).state!,
- myName: ref.read(pFrostMyName.state).state!,
- );
-
- await Navigator.of(context).pushNamed(
- FrostShareCommitmentsView.routeName,
- arguments: (
- walletName: widget.walletName,
- coin: widget.coin,
- ),
- );
- },
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart
new file mode 100644
index 000000000..0f1cd84aa
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1a.dart
@@ -0,0 +1,248 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:qr_flutter/qr_flutter.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+import 'package:stackwallet/widgets/detail_item.dart';
+import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
+import 'package:stackwallet/widgets/frost_step_user_steps.dart';
+
+class FrostCreateStep1a extends ConsumerStatefulWidget {
+ const FrostCreateStep1a({super.key});
+
+ static const String routeName = "/frostCreateStep1a";
+ static const String title = "Multisig group info";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep1aState();
+}
+
+class _FrostCreateStep1aState extends ConsumerState {
+ static const info = [
+ "Share this config with the group participants.",
+ "Wait for them to join the group.",
+ "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 “Generate keys”.",
+ ];
+
+ bool _userVerifyContinue = false;
+
+ void _showParticipantsDialog() {
+ final participants = Frost.getParticipants(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ );
+
+ showDialog(
+ context: context,
+ builder: (_) => SimpleMobileDialog(
+ showCloseButton: false,
+ padding: EdgeInsets.zero,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(
+ height: 24,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Text(
+ "Group participants",
+ style: STextStyles.w600_20(context),
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Text(
+ "The names are case-sensitive and must be entered exactly.",
+ style: STextStyles.w400_16(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ for (final participant in participants)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: double.infinity,
+ height: 1.5,
+ color:
+ Theme.of(context).extension()!.background,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Row(
+ children: [
+ Container(
+ width: 26,
+ height: 26,
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldActiveBG,
+ borderRadius: BorderRadius.circular(
+ 200,
+ ),
+ ),
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.user,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: Text(
+ participant,
+ style: STextStyles.w500_14(context),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ IconCopyButton(
+ data: participant,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ SizedBox(
+ height: 220,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ QrImageView(
+ data: ref.watch(pFrostMultisigConfig.state).state ?? "Error",
+ size: 220,
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ foregroundColor: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(
+ height: 20,
+ ),
+ DetailItem(
+ title: "Encoded config",
+ detail: ref.watch(pFrostMultisigConfig.state).state ?? "Error",
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data:
+ ref.watch(pFrostMultisigConfig.state).state ?? "Error",
+ )
+ : SimpleCopyButton(
+ data:
+ ref.watch(pFrostMultisigConfig.state).state ?? "Error",
+ ),
+ ),
+ SizedBox(
+ height: Util.isDesktop ? 64 : 16,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Show group participants",
+ onPressed: _showParticipantsDialog,
+ ),
+ ),
+ ],
+ ),
+ if (!Util.isDesktop)
+ const Spacer(
+ flex: 2,
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has joined the group",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Start key generation",
+ enabled: _userVerifyContinue,
+ onPressed: () async {
+ ref.read(pFrostStartKeyGenData.notifier).state =
+ Frost.startKeyGeneration(
+ multisigConfig: ref.watch(pFrostMultisigConfig.state).state!,
+ myName: ref.read(pFrostMyName.state).state!,
+ );
+
+ ref.read(pFrostCreateCurrentStep.state).state = 2;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ // FrostShareCommitmentsView.routeName,
+ );
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart
new file mode 100644
index 000000000..21ed92a7d
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart
@@ -0,0 +1,184 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/frost_step_user_steps.dart';
+import 'package:stackwallet/widgets/rounded_white_container.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostCreateStep1b extends ConsumerStatefulWidget {
+ const FrostCreateStep1b({super.key});
+
+ static const String routeName = "/frostCreateStep1b";
+ static const String title = "Import group info";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep1bState();
+}
+
+class _FrostCreateStep1bState extends ConsumerState {
+ static const info = [
+ "Scan the config QR code or paste the code provided by the group creator.",
+ "Enter your name EXACTLY as the group creator entered it. When in doubt, "
+ "double check with them. The names are case-sensitive.",
+ "Wait for other participants to finish entering their information.",
+ "Verify that everyone has filled out their forms before continuing. If you "
+ "try to continue before everyone is ready, the process will be canceled.",
+ "Check the box and press “Generate keys”.",
+ ];
+
+ late final TextEditingController myNameFieldController, configFieldController;
+ late final FocusNode myNameFocusNode, configFocusNode;
+
+ bool _nameEmpty = true, _configEmpty = true, _userVerifyContinue = false;
+
+ @override
+ void initState() {
+ myNameFieldController = TextEditingController();
+ configFieldController = TextEditingController();
+ myNameFocusNode = FocusNode();
+ configFocusNode = FocusNode();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ myNameFieldController.dispose();
+ configFieldController.dispose();
+ myNameFocusNode.dispose();
+ configFocusNode.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ FrostStepField(
+ controller: configFieldController,
+ focusNode: configFocusNode,
+ showQrScanOption: true,
+ label: "Enter config",
+ hint: "Enter config",
+ onChanged: (_) {
+ setState(() {
+ _configEmpty = configFieldController.text.isEmpty;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ FrostStepField(
+ controller: myNameFieldController,
+ focusNode: myNameFocusNode,
+ showQrScanOption: false,
+ label: "My name",
+ hint: "Enter your name",
+ onChanged: (_) {
+ setState(() {
+ _nameEmpty = myNameFieldController.text.isEmpty;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 6,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: RoundedWhiteContainer(
+ child: Text(
+ "Enter your name EXACTLY as the group creator entered it. "
+ "The names are case-sensitive.",
+ style: STextStyles.label(context),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has joined the group",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Start key generation",
+ enabled: _userVerifyContinue && !_nameEmpty && !_configEmpty,
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ }
+
+ final config = configFieldController.text;
+
+ if (!Frost.validateEncodedMultisigConfig(encodedConfig: config)) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Invalid config",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+
+ if (!Frost.getParticipants(multisigConfig: config)
+ .contains(myNameFieldController.text)) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "My name not found in config participants",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+
+ ref.read(pFrostMyName.state).state = myNameFieldController.text;
+ ref.read(pFrostMultisigConfig.notifier).state = config;
+
+ ref.read(pFrostStartKeyGenData.state).state =
+ Frost.startKeyGeneration(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ myName: ref.read(pFrostMyName.state).state!,
+ );
+ ref.read(pFrostCreateCurrentStep.state).state = 2;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ },
+ )
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart
new file mode 100644
index 000000000..181a7c3d6
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart
@@ -0,0 +1,200 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.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/frost_step_user_steps.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostCreateStep2 extends ConsumerStatefulWidget {
+ const FrostCreateStep2({
+ super.key,
+ });
+
+ static const String routeName = "/frostCreateStep2";
+ static const String title = "Commitments";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep2State();
+}
+
+class _FrostCreateStep2State extends ConsumerState {
+ static const info = [
+ "Share your commitment with other group members.",
+ "Enter their commitments into the corresponding fields.",
+ ];
+
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final List participants;
+ late final String myCommitment;
+ late final int myIndex;
+
+ final List fieldIsEmptyFlags = [];
+ bool _userVerifyContinue = false;
+
+ @override
+ void initState() {
+ participants = Frost.getParticipants(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ );
+ myIndex = participants.indexOf(ref.read(pFrostMyName.state).state!);
+ myCommitment = ref.read(pFrostStartKeyGenData.state).state!.commitments;
+
+ // temporarily remove my name
+ participants.removeAt(myIndex);
+
+ for (int i = 0; i < participants.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "My name",
+ detail: ref.watch(pFrostMyName.state).state!,
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "My commitment",
+ detail: myCommitment,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: myCommitment,
+ )
+ : SimpleCopyButton(
+ data: myCommitment,
+ ),
+ ),
+ const SizedBox(height: 12),
+ FrostQrDialogPopupButton(
+ data: myCommitment,
+ ),
+ const SizedBox(height: 12),
+ for (int i = 0; i < participants.length; i++)
+ Padding(
+ padding: const EdgeInsets.only(top: 12),
+ child: FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: participants[i],
+ hint: "Enter ${participants[i]}'s commitment",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(height: 12),
+ CheckboxTextButton(
+ label: "I have verified that everyone has my commitment",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(height: 12),
+ PrimaryButton(
+ label: "Generate shares",
+ enabled: _userVerifyContinue &&
+ !fieldIsEmptyFlags.reduce((v, e) => v |= e),
+ onPressed: () async {
+ // check for empty commitments
+ if (controllers
+ .map((e) => e.text.isEmpty)
+ .reduce((value, element) => value |= element)) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Missing commitments",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+
+ // collect commitment strings and insert my own at the correct index
+ final commitments = controllers.map((e) => e.text).toList();
+ commitments.insert(myIndex, myCommitment);
+
+ try {
+ ref.read(pFrostSecretSharesData.notifier).state =
+ Frost.generateSecretShares(
+ multisigConfigWithNamePtr: ref
+ .read(pFrostStartKeyGenData.state)
+ .state!
+ .multisigConfigWithNamePtr,
+ mySeed: ref.read(pFrostStartKeyGenData.state).state!.seed,
+ secretShareMachineWrapperPtr: ref
+ .read(pFrostStartKeyGenData.state)
+ .state!
+ .secretShareMachineWrapperPtr,
+ commitments: commitments,
+ );
+
+ ref.read(pFrostCreateCurrentStep.state).state = 3;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+ if (context.mounted) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Failed to generate shares",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ }
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart
new file mode 100644
index 000000000..782d86135
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart
@@ -0,0 +1,200 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.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/frost_step_user_steps.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostCreateStep3 extends ConsumerStatefulWidget {
+ const FrostCreateStep3({super.key});
+
+ static const String routeName = "/frostCreateStep3";
+ static const String title = "Shares";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep3State();
+}
+
+class _FrostCreateStep3State extends ConsumerState {
+ static const info = [
+ "Send your share to other group members.",
+ "Enter their shares into the corresponding fields.",
+ ];
+
+ bool _userVerifyContinue = false;
+
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final List participants;
+ late final String myShare;
+ late final int myIndex;
+
+ final List fieldIsEmptyFlags = [];
+
+ @override
+ void initState() {
+ participants = Frost.getParticipants(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ );
+ myIndex = participants.indexOf(ref.read(pFrostMyName.state).state!);
+ myShare = ref.read(pFrostSecretSharesData.state).state!.share;
+
+ // temporarily remove my name. Added back later
+ participants.removeAt(myIndex);
+
+ for (int i = 0; i < participants.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "My name",
+ detail: ref.watch(pFrostMyName.state).state!,
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "My share",
+ detail: myShare,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: myShare,
+ )
+ : SimpleCopyButton(
+ data: myShare,
+ ),
+ ),
+ const SizedBox(height: 12),
+ FrostQrDialogPopupButton(
+ data: myShare,
+ ),
+ const SizedBox(height: 12),
+ for (int i = 0; i < participants.length; i++)
+ Padding(
+ padding: const EdgeInsets.only(top: 12),
+ child: FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: participants[i],
+ hint: "Enter ${participants[i]}'s share",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(height: 12),
+ CheckboxTextButton(
+ label: "I have verified that everyone has my share",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Generate",
+ enabled: _userVerifyContinue &&
+ !fieldIsEmptyFlags.reduce((v, e) => v |= e),
+ onPressed: () async {
+ // check for empty commitments
+ if (controllers
+ .map((e) => e.text.isEmpty)
+ .reduce((value, element) => value |= element)) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Missing shares",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+
+ // collect commitment strings and insert my own at the correct index
+ final shares = controllers.map((e) => e.text).toList();
+ shares.insert(myIndex, myShare);
+
+ try {
+ ref.read(pFrostCompletedKeyGenData.notifier).state =
+ Frost.completeKeyGeneration(
+ multisigConfigWithNamePtr: ref
+ .read(pFrostStartKeyGenData.state)
+ .state!
+ .multisigConfigWithNamePtr,
+ secretSharesResPtr: ref
+ .read(pFrostSecretSharesData.state)
+ .state!
+ .secretSharesResPtr,
+ shares: shares,
+ );
+
+ 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,
+ );
+
+ if (context.mounted) {
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Failed to complete key generation",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ }
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart
new file mode 100644
index 000000000..864e905bf
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart
@@ -0,0 +1,77 @@
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/utilities/util.dart';
+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/frost_step_user_steps.dart';
+
+class FrostCreateStep4 extends ConsumerStatefulWidget {
+ const FrostCreateStep4({super.key});
+
+ static const String routeName = "/frostCreateStep4";
+ static const String title = "Verify multisig ID";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep4State();
+}
+
+class _FrostCreateStep4State extends ConsumerState {
+ static const info = [
+ "Ensure your multisig ID matches that of each other participant.",
+ ];
+
+ late final Uint8List multisigId;
+
+ @override
+ void initState() {
+ multisigId = ref.read(pFrostCompletedKeyGenData.state).state!.multisigId;
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "Multisig ID",
+ detail: multisigId.toString(),
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: multisigId.toString(),
+ )
+ : SimpleCopyButton(
+ data: multisigId.toString(),
+ ),
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(height: 12),
+ PrimaryButton(
+ label: "Confirm",
+ onPressed: () {
+ ref.read(pFrostCreateCurrentStep.state).state = 5;
+ Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ },
+ )
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart
new file mode 100644
index 000000000..586a1c189
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_5.dart
@@ -0,0 +1,239 @@
+import 'dart:async';
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/notifications/show_flush_bar.dart';
+import 'package:stackwallet/pages/home_view/home_view.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
+import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/providers/global/node_service_provider.dart';
+import 'package:stackwallet/providers/global/prefs_provider.dart';
+import 'package:stackwallet/providers/global/secure_store_provider.dart';
+import 'package:stackwallet/providers/global/wallets_provider.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
+import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
+import 'package:stackwallet/wallets/wallet/wallet.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.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/loading_indicator.dart';
+import 'package:stackwallet/widgets/rounded_container.dart';
+
+class FrostCreateStep5 extends ConsumerStatefulWidget {
+ const FrostCreateStep5({super.key});
+
+ static const String routeName = "/frostCreateStep5";
+ static const String title = "Back up your keys";
+
+ @override
+ ConsumerState createState() => _FrostCreateStep5State();
+}
+
+class _FrostCreateStep5State extends ConsumerState {
+ static const _warning = "These are your private keys. Please back them up, "
+ "keep them safe and never share it with anyone. Your private keys are the"
+ " only way you can access your funds if you forget PIN, lose your phone, "
+ "etc. Stack Wallet does not keep nor is able to restore your private keys"
+ ".";
+
+ late final String seed, recoveryString, serializedKeys, multisigConfig;
+ late final Uint8List multisigId;
+
+ bool _userVerifyContinue = false;
+
+ @override
+ void initState() {
+ seed = ref.read(pFrostStartKeyGenData.state).state!.seed;
+ serializedKeys =
+ ref.read(pFrostCompletedKeyGenData.state).state!.serializedKeys;
+ recoveryString =
+ ref.read(pFrostCompletedKeyGenData.state).state!.recoveryString;
+ multisigConfig = ref.read(pFrostMultisigConfig.state).state!;
+ multisigId = ref.read(pFrostCompletedKeyGenData.state).state!.multisigId;
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ children: [
+ RoundedContainer(
+ color:
+ Theme.of(context).extension()!.warningBackground,
+ child: Text(
+ _warning,
+ style: STextStyles.w500_14(context).copyWith(
+ color: Theme.of(context)
+ .extension()!
+ .warningForeground,
+ ),
+ ),
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "Multisig Config",
+ detail: multisigConfig,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: multisigConfig,
+ )
+ : SimpleCopyButton(
+ data: multisigConfig,
+ ),
+ ),
+ const SizedBox(height: 12),
+ DetailItem(
+ title: "Keys",
+ detail: serializedKeys,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: serializedKeys,
+ )
+ : SimpleCopyButton(
+ data: serializedKeys,
+ ),
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(height: 12),
+ CheckboxTextButton(
+ label: "I have backed up my keys and the config",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(height: 12),
+ PrimaryButton(
+ label: "Continue",
+ enabled: _userVerifyContinue,
+ onPressed: () async {
+ bool progressPopped = false;
+ try {
+ unawaited(
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ useSafeArea: true,
+ builder: (ctx) {
+ return const Center(
+ child: LoadingIndicator(
+ width: 50,
+ height: 50,
+ ),
+ );
+ },
+ ),
+ );
+
+ final data = ref.read(pFrostScaffoldArgs)!;
+
+ final info = WalletInfo.createNew(
+ coin: data.info.frostCurrency.coin,
+ name: data.info.walletName,
+ );
+
+ final wallet = await Wallet.create(
+ walletInfo: info,
+ mainDB: ref.read(mainDBProvider),
+ secureStorageInterface: ref.read(secureStoreProvider),
+ nodeService: ref.read(nodeServiceChangeNotifierProvider),
+ prefs: ref.read(prefsChangeNotifierProvider),
+ mnemonic: seed,
+ mnemonicPassphrase: "",
+ );
+
+ await (wallet as BitcoinFrostWallet).initializeNewFrost(
+ multisigConfig: multisigConfig,
+ recoveryString: recoveryString,
+ serializedKeys: serializedKeys,
+ multisigId: multisigId,
+ myName: ref.read(pFrostMyName.state).state!,
+ participants: Frost.getParticipants(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ ),
+ threshold: Frost.getThreshold(
+ multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
+ ),
+ );
+
+ await info.setMnemonicVerified(
+ isar: ref.read(mainDBProvider).isar,
+ );
+
+ ref.read(pWallets).addWallet(wallet);
+
+ // pop progress dialog
+ if (context.mounted) {
+ Navigator.pop(context);
+ progressPopped = true;
+ }
+
+ if (mounted) {
+ ref.read(pFrostScaffoldCanPopDesktop.notifier).state = true;
+ final nav = ref.read(pFrostScaffoldArgs)!.parentNav;
+
+ if (Util.isDesktop) {
+ nav.popUntil(
+ ModalRoute.withName(
+ DesktopHomeView.routeName,
+ ),
+ );
+ } else {
+ unawaited(
+ nav.pushNamedAndRemoveUntil(
+ HomeView.routeName,
+ (route) => false,
+ ),
+ );
+ }
+
+ ref.read(pFrostMultisigConfig.state).state = null;
+ ref.read(pFrostStartKeyGenData.state).state = null;
+ ref.read(pFrostSecretSharesData.state).state = null;
+ ref.read(pFrostScaffoldArgs.state).state = null;
+
+ unawaited(
+ showFloatingFlushBar(
+ type: FlushBarType.success,
+ message: "Your wallet is set up.",
+ iconAsset: Assets.svg.check,
+ context: nav.context,
+ ),
+ );
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ // pop progress dialog
+ if (context.mounted && !progressPopped) {
+ Navigator.pop(context);
+ progressPopped = true;
+ }
+ // TODO: handle gracefully
+ rethrow;
+ }
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart
new file mode 100644
index 000000000..46f272daf
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1a.dart
@@ -0,0 +1,309 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:qr_flutter/qr_flutter.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/providers/global/wallets_provider.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/themes/stack_colors.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
+import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
+import 'package:stackwallet/widgets/detail_item.dart';
+import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
+import 'package:stackwallet/widgets/frost_step_user_steps.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+
+class FrostReshareStep1a extends ConsumerStatefulWidget {
+ const FrostReshareStep1a({super.key});
+
+ static const String routeName = "/frostReshareStep1a";
+ static const String title = "Resharer config";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep1aState();
+}
+
+class _FrostReshareStep1aState extends ConsumerState {
+ static const info = [
+ "Share this config with the signing group participants as well as any new "
+ "participant.",
+ "Wait for them to import the config.",
+ "Verify that everyone has imported the config. If you try to continue "
+ "before everyone is ready, the process will be canceled.",
+ "Check the box and press “Start resharing”.",
+ ];
+
+ late final bool iAmInvolved;
+
+ bool _buttonLock = false;
+ bool _userVerifyContinue = false;
+
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ final wallet =
+ ref.read(pWallets).getWallet(ref.read(pFrostScaffoldArgs)!.walletId!)
+ as BitcoinFrostWallet;
+
+ final serializedKeys = await wallet.getSerializedKeys();
+ if (mounted) {
+ final result = Frost.beginResharer(
+ serializedKeys: serializedKeys!,
+ config: Frost.decodeRConfig(
+ ref.read(pFrostResharingData).resharerRConfig!,
+ ),
+ );
+
+ ref.read(pFrostResharingData).startResharerData = result;
+
+ ref.read(pFrostCreateCurrentStep.state).state = 2;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ void _showParticipantsDialog() {
+ final participants =
+ ref.read(pFrostResharingData).configData!.newParticipants;
+
+ showDialog(
+ context: context,
+ builder: (_) => SimpleMobileDialog(
+ showCloseButton: false,
+ padding: EdgeInsets.zero,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(
+ height: 24,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Text(
+ "Group participants",
+ style: STextStyles.w600_20(context),
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Text(
+ "The names are case-sensitive and must be entered exactly.",
+ style: STextStyles.w400_16(context).copyWith(
+ color: Theme.of(context).extension()!.textDark3,
+ ),
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ for (final participant in participants)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: double.infinity,
+ height: 1.5,
+ color:
+ Theme.of(context).extension()!.background,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 24),
+ child: Row(
+ children: [
+ Container(
+ width: 26,
+ height: 26,
+ decoration: BoxDecoration(
+ color: Theme.of(context)
+ .extension()!
+ .textFieldActiveBG,
+ borderRadius: BorderRadius.circular(
+ 200,
+ ),
+ ),
+ child: Center(
+ child: SvgPicture.asset(
+ Assets.svg.user,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ Expanded(
+ child: Text(
+ participant,
+ style: STextStyles.w500_14(context),
+ ),
+ ),
+ const SizedBox(
+ width: 8,
+ ),
+ IconCopyButton(
+ data: participant,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ @override
+ void initState() {
+ // TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
+ final frostInfo = ref
+ .read(mainDBProvider)
+ .isar
+ .frostWalletInfo
+ .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!;
+
+ final myOldIndex = frostInfo.participants.indexOf(frostInfo.myName);
+
+ iAmInvolved = ref
+ .read(pFrostResharingData)
+ .configData!
+ .resharers
+ .values
+ .contains(myOldIndex);
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(height: 20),
+ SizedBox(
+ height: 220,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ QrImageView(
+ data: ref.watch(pFrostResharingData).resharerRConfig!,
+ size: 220,
+ backgroundColor:
+ Theme.of(context).extension()!.background,
+ foregroundColor: Theme.of(context)
+ .extension()!
+ .accentColorDark,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(
+ height: 32,
+ ),
+ DetailItem(
+ title: "Config",
+ detail: ref.watch(pFrostResharingData).resharerRConfig!,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: ref.watch(pFrostResharingData).resharerRConfig!,
+ )
+ : SimpleCopyButton(
+ data: ref.watch(pFrostResharingData).resharerRConfig!,
+ ),
+ ),
+ SizedBox(
+ height: Util.isDesktop ? 64 : 16,
+ ),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ label: "Show group participants",
+ onPressed: _showParticipantsDialog,
+ ),
+ ),
+ ],
+ ),
+ if (iAmInvolved && !Util.isDesktop) const Spacer(),
+ if (iAmInvolved)
+ const SizedBox(
+ height: 16,
+ ),
+ if (iAmInvolved)
+ CheckboxTextButton(
+ label: "I have verified that everyone has imported the config",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ if (iAmInvolved)
+ const SizedBox(
+ height: 16,
+ ),
+ if (iAmInvolved)
+ PrimaryButton(
+ label: "Start resharing",
+ enabled: _userVerifyContinue,
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart
new file mode 100644
index 000000000..71fe0e737
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1b.dart
@@ -0,0 +1,211 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:frostdart/frostdart.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/providers/global/secure_store_provider.dart';
+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/isar/models/frost_wallet_info.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.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 FrostReshareStep1b extends ConsumerStatefulWidget {
+ const FrostReshareStep1b({
+ super.key,
+ });
+
+ static const String routeName = "/frostReshareStep1b";
+ static const String title = "Import reshare config";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep1bState();
+}
+
+class _FrostReshareStep1bState extends ConsumerState {
+ static const info = [
+ "Scan the config QR code or paste the code provided by the group member who"
+ " is initiating resharing.",
+ "Wait for other participants to finish importing the config.",
+ "Verify that everyone has filled out their forms before continuing. If you "
+ "try to continue before everyone is ready, the process will be canceled.",
+ "Check the box and press “Start resharing”.",
+ ];
+
+ late final TextEditingController configFieldController;
+ late final FocusNode configFocusNode;
+
+ bool _configEmpty = true;
+
+ bool _buttonLock = false;
+ bool _userVerifyContinue = false;
+
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ final walletId = ref.read(pFrostScaffoldArgs)!.walletId!;
+ // TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
+ final frostInfo = ref
+ .read(mainDBProvider)
+ .isar
+ .frostWalletInfo
+ .getByWalletIdSync(walletId)!;
+
+ ref.read(pFrostResharingData).reset();
+ ref.read(pFrostResharingData).myName = frostInfo.myName;
+ ref.read(pFrostResharingData).resharerRConfig =
+ configFieldController.text;
+
+ String? salt;
+ try {
+ salt = Format.uint8listToString(
+ resharerSalt(
+ resharerConfig: Frost.decodeRConfig(
+ ref.read(pFrostResharingData).resharerRConfig!,
+ ),
+ ),
+ );
+ } catch (_) {
+ throw Exception("Bad resharer config");
+ }
+
+ if (frostInfo.knownSalts.contains(salt)) {
+ throw Exception("Duplicate config salt");
+ } else {
+ final salts = frostInfo.knownSalts.toList();
+ salts.add(salt);
+ final mainDB = ref.read(mainDBProvider);
+ await mainDB.isar.writeTxn(() async {
+ final id = frostInfo.id;
+ await mainDB.isar.frostWalletInfo.delete(id);
+ await mainDB.isar.frostWalletInfo.put(
+ frostInfo.copyWith(knownSalts: salts),
+ );
+ });
+ }
+
+ final serializedKeys = await ref.read(secureStoreProvider).read(
+ key: "{$walletId}_serializedFROSTKeys",
+ );
+ if (mounted) {
+ final result = Frost.beginResharer(
+ serializedKeys: serializedKeys!,
+ config: Frost.decodeRConfig(
+ ref.read(pFrostResharingData).resharerRConfig!,
+ ),
+ );
+
+ ref.read(pFrostResharingData).startResharerData = result;
+
+ ref.read(pFrostCreateCurrentStep.state).state = 2;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ @override
+ void initState() {
+ configFieldController = TextEditingController();
+ configFocusNode = FocusNode();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ configFieldController.dispose();
+ configFocusNode.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(
+ height: 16,
+ ),
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(height: 20),
+ FrostStepField(
+ controller: configFieldController,
+ focusNode: configFocusNode,
+ showQrScanOption: true,
+ label: "Enter config",
+ hint: "Enter config",
+ onChanged: (_) {
+ setState(() {
+ _configEmpty = configFieldController.text.isEmpty;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has imported the config",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Start resharing",
+ enabled: !_configEmpty && _userVerifyContinue,
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ }
+
+ await _onPressed();
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart
new file mode 100644
index 000000000..adc1c7f5e
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_1c.dart
@@ -0,0 +1,228 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/show_loading.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
+import 'package:stackwallet/wallets/models/incomplete_frost_wallet.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.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 FrostReshareStep1c extends ConsumerStatefulWidget {
+ const FrostReshareStep1c({super.key});
+
+ static const String routeName = "/frostReshareStep1c";
+ static const String title = "Import reshare config";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep1cState();
+}
+
+class _FrostReshareStep1cState extends ConsumerState {
+ static const info = [
+ "Scan the config QR code or paste the code provided by the group creator.",
+ "Enter your name EXACTLY as the group creator entered it. When in doubt, "
+ "double check with them. The names are case-sensitive.",
+ "Wait for other participants to finish entering their information.",
+ "Verify that everyone has filled out their forms before continuing. If you "
+ "try to continue before everyone is ready, the process could be canceled.",
+ "Check the box and press “Join group”.",
+ ];
+
+ late final TextEditingController myNameFieldController, configFieldController;
+ late final FocusNode myNameFocusNode, configFocusNode;
+
+ bool _nameEmpty = true,
+ _configEmpty = true,
+ _userVerifyContinue = false,
+ _buttonLock = false;
+
+ Future _createWallet() async {
+ final data = ref.read(pFrostScaffoldArgs)!;
+
+ final info = WalletInfo.createNew(
+ name: data.info.walletName,
+ coin: data.info.frostCurrency.coin,
+ );
+
+ final wallet = IncompleteFrostWallet();
+ wallet.info = info;
+
+ return wallet;
+ }
+
+ @override
+ void initState() {
+ myNameFieldController = TextEditingController();
+ configFieldController = TextEditingController();
+ myNameFocusNode = FocusNode();
+ configFocusNode = FocusNode();
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ myNameFieldController.dispose();
+ configFieldController.dispose();
+ myNameFocusNode.dispose();
+ configFocusNode.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ const FrostStepUserSteps(
+ userSteps: info,
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ FrostStepField(
+ controller: myNameFieldController,
+ focusNode: myNameFocusNode,
+ showQrScanOption: false,
+ label: "My name",
+ hint: "Enter your name",
+ onChanged: (_) {
+ setState(() {
+ _nameEmpty = myNameFieldController.text.isEmpty;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ FrostStepField(
+ controller: configFieldController,
+ focusNode: configFocusNode,
+ showQrScanOption: true,
+ label: "Enter config",
+ hint: "Enter config",
+ onChanged: (_) {
+ setState(() {
+ _configEmpty = configFieldController.text.isEmpty;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has joined the group",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Join group",
+ enabled: _userVerifyContinue && !_nameEmpty && !_configEmpty,
+ onPressed: () async {
+ if (FocusScope.of(context).hasFocus) {
+ FocusScope.of(context).unfocus();
+ }
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ ref.read(pFrostResharingData).reset();
+ ref.read(pFrostResharingData).myName =
+ myNameFieldController.text;
+ ref.read(pFrostResharingData).resharerRConfig =
+ configFieldController.text;
+
+ if (!ref
+ .read(pFrostResharingData)
+ .configData!
+ .newParticipants
+ .contains(ref.read(pFrostResharingData).myName!)) {
+ ref.read(pFrostResharingData).reset();
+ return await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "My name not found in config participants",
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+
+ Exception? ex;
+ final wallet = await showLoading(
+ whileFuture: _createWallet(),
+ context: context,
+ message: "Setting up wallet",
+ rootNavigator: true,
+ onException: (e) => ex = e,
+ );
+
+ if (ex != null) {
+ throw ex!;
+ }
+
+ if (context.mounted) {
+ ref.read(pFrostResharingData).incompleteWallet = wallet!;
+ final data = ref.read(pFrostScaffoldArgs)!;
+ ref.read(pFrostScaffoldArgs.state).state = (
+ info: data.info,
+ walletId: wallet.walletId,
+ stepRoutes: data.stepRoutes,
+ parentNav: data.parentNav,
+ frostInterruptionDialogType:
+ FrostInterruptionDialogType.resharing,
+ );
+ ref.read(pFrostMyName.state).state =
+ ref.read(pFrostResharingData).myName!;
+ ref.read(pFrostCreateCurrentStep.state).state = 2;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ if (context.mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ },
+ )
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart
new file mode 100644
index 000000000..63edc03cf
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2abd.dart
@@ -0,0 +1,218 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.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';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostReshareStep2abd extends ConsumerStatefulWidget {
+ const FrostReshareStep2abd({super.key});
+
+ static const String routeName = "/FrostReshareStep2abd";
+ static const String title = "Resharers";
+
+ @override
+ ConsumerState createState() =>
+ _FrostReshareStep2abdState();
+}
+
+class _FrostReshareStep2abdState extends ConsumerState {
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final Map resharers;
+ late final int myResharerIndexIndex;
+ late final String myResharerStart;
+ late final bool amOutgoingParticipant;
+
+ final List fieldIsEmptyFlags = [];
+
+ bool _buttonLock = false;
+
+ bool _userVerifyContinue = false;
+
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ if (!amOutgoingParticipant) {
+ // collect resharer strings
+ final resharerStarts = controllers.map((e) => e.text).toList();
+ if (myResharerIndexIndex >= 0) {
+ // only insert my own at the correct index if I am a resharer
+ resharerStarts.insert(myResharerIndexIndex, myResharerStart);
+ }
+
+ final result = Frost.beginReshared(
+ myName: ref.read(pFrostResharingData).myName!,
+ resharerConfig: Frost.decodeRConfig(
+ ref.read(pFrostResharingData).resharerRConfig!,
+ ),
+ resharerStarts: resharerStarts,
+ );
+
+ ref.read(pFrostResharingData).startResharedData = result;
+ }
+
+ ref.read(pFrostCreateCurrentStep.state).state = 3;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ @override
+ void initState() {
+ // TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
+ final frostInfo = ref
+ .read(mainDBProvider)
+ .isar
+ .frostWalletInfo
+ .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!;
+ final myOldIndex =
+ frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!);
+
+ myResharerStart =
+ ref.read(pFrostResharingData).startResharerData!.resharerStart;
+
+ resharers = ref.read(pFrostResharingData).configData!.resharers;
+ myResharerIndexIndex = resharers.values.toList().indexOf(myOldIndex);
+ if (myResharerIndexIndex >= 0) {
+ // remove my name for now as we don't need a text field for it
+ resharers.remove(ref.read(pFrostResharingData).myName!);
+ }
+
+ amOutgoingParticipant = !ref
+ .read(pFrostResharingData)
+ .configData!
+ .newParticipants
+ .contains(ref.read(pFrostResharingData).myName!);
+
+ for (int i = 0; i < resharers.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ DetailItem(
+ title: "My resharer",
+ detail: myResharerStart,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: myResharerStart,
+ )
+ : SimpleCopyButton(
+ data: myResharerStart,
+ ),
+ ),
+ const SizedBox(height: 12),
+ FrostQrDialogPopupButton(
+ data: myResharerStart,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ for (int i = 0; i < resharers.length; i++)
+ FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: resharers.keys.elementAt(i),
+ hint: "Enter "
+ "${resharers.keys.elementAt(i)}"
+ "'s resharer",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ],
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 12,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has my resharer",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Continue",
+ enabled: _userVerifyContinue &&
+ (amOutgoingParticipant ||
+ !fieldIsEmptyFlags.reduce((v, e) => v |= e)),
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart
new file mode 100644
index 000000000..0c811c635
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_2c.dart
@@ -0,0 +1,145 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostReshareStep2c extends ConsumerStatefulWidget {
+ const FrostReshareStep2c({super.key});
+
+ static const String routeName = "/FrostReshareStep2c";
+ static const String title = "Resharers";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep2cState();
+}
+
+class _FrostReshareStep2cState extends ConsumerState {
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final Map resharers;
+
+ final List fieldIsEmptyFlags = [];
+
+ bool _buttonLock = false;
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ // collect resharer strings
+ final resharerStarts = controllers.map((e) => e.text).toList();
+
+ final result = Frost.beginReshared(
+ myName: ref.read(pFrostResharingData).myName!,
+ resharerConfig: Frost.decodeRConfig(
+ ref.read(pFrostResharingData).resharerRConfig!,
+ ),
+ resharerStarts: resharerStarts,
+ );
+
+ ref.read(pFrostResharingData).startResharedData = result;
+
+ ref.read(pFrostCreateCurrentStep.state).state = 3;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ @override
+ void initState() {
+ resharers = ref.read(pFrostResharingData).configData!.resharers;
+
+ for (int i = 0; i < resharers.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ for (int i = 0; i < resharers.length; i++)
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: resharers.keys.elementAt(i),
+ hint: "Enter "
+ "${resharers.keys.elementAt(i)}"
+ "'s resharer",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Continue",
+ enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e),
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart
new file mode 100644
index 000000000..5bccf7756
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3abd.dart
@@ -0,0 +1,208 @@
+import 'dart:ffi';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.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';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+class FrostReshareStep3abd extends ConsumerStatefulWidget {
+ const FrostReshareStep3abd({super.key});
+
+ static const String routeName = "/frostReshareStep3abd";
+ static const String title = "Encryption keys";
+
+ @override
+ ConsumerState createState() =>
+ _FrostReshareStep3abdState();
+}
+
+class _FrostReshareStep3abdState extends ConsumerState {
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final List newParticipants;
+ late final int myIndex;
+ late final String? myEncryptionKey;
+ late final bool amOutgoingParticipant;
+
+ final List fieldIsEmptyFlags = [];
+
+ bool _userVerifyContinue = false;
+
+ bool _buttonLock = false;
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ // collect encryptionKeys strings and insert my own at the correct index
+ final encryptionKeys = controllers.map((e) => e.text).toList();
+ if (!amOutgoingParticipant) {
+ encryptionKeys.insert(myIndex, myEncryptionKey!);
+ }
+
+ final result = Frost.finishResharer(
+ machine: ref.read(pFrostResharingData).startResharerData!.machine.ref,
+ encryptionKeysOfResharedTo: encryptionKeys,
+ );
+
+ ref.read(pFrostResharingData).resharerComplete = result;
+
+ ref.read(pFrostCreateCurrentStep.state).state = 4;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ @override
+ void initState() {
+ myEncryptionKey =
+ ref.read(pFrostResharingData).startResharedData?.resharedStart;
+
+ newParticipants = ref.read(pFrostResharingData).configData!.newParticipants;
+ myIndex = newParticipants.indexOf(ref.read(pFrostResharingData).myName!);
+
+ if (myIndex >= 0) {
+ // remove my name for now as we don't need a text field for it
+ newParticipants.removeAt(myIndex);
+ }
+
+ if (myEncryptionKey == null && myIndex == -1) {
+ amOutgoingParticipant = true;
+ } else if (myEncryptionKey != null && myIndex >= 0) {
+ amOutgoingParticipant = false;
+ } else {
+ throw Exception("Invalid resharing state");
+ }
+
+ for (int i = 0; i < newParticipants.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ if (!amOutgoingParticipant)
+ DetailItem(
+ title: "My encryption key",
+ detail: myEncryptionKey!,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: myEncryptionKey!,
+ )
+ : SimpleCopyButton(
+ data: myEncryptionKey!,
+ ),
+ ),
+ if (!amOutgoingParticipant) const SizedBox(height: 12),
+ if (!amOutgoingParticipant)
+ FrostQrDialogPopupButton(
+ data: myEncryptionKey!,
+ ),
+ if (!amOutgoingParticipant)
+ const SizedBox(
+ height: 12,
+ ),
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ for (int i = 0; i < newParticipants.length; i++)
+ Padding(
+ padding: const EdgeInsets.only(top: 12),
+ child: FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: newParticipants[i],
+ hint: "Enter "
+ "${newParticipants[i]}"
+ "'s encryption key",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ if (!amOutgoingParticipant)
+ const SizedBox(
+ height: 12,
+ ),
+ if (!amOutgoingParticipant)
+ CheckboxTextButton(
+ label: "I have verified that everyone has my encryption key",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Continue",
+ enabled: (amOutgoingParticipant || _userVerifyContinue) &&
+ !fieldIsEmptyFlags.reduce((v, e) => v |= e),
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart
new file mode 100644
index 000000000..3bde7bc76
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_3c.dart
@@ -0,0 +1,87 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.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/utilities/util.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/detail_item.dart';
+
+class FrostReshareStep3c extends ConsumerStatefulWidget {
+ const FrostReshareStep3c({super.key});
+
+ static const String routeName = "/frostReshareStep3c";
+ static const String title = "Encryption keys";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep3cState();
+}
+
+class _FrostReshareStep3cState extends ConsumerState {
+ bool _userVerifyContinue = false;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ DetailItem(
+ title: "My encryption key",
+ detail:
+ ref.watch(pFrostResharingData).startResharedData!.resharedStart,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: ref
+ .watch(pFrostResharingData)
+ .startResharedData!
+ .resharedStart,
+ )
+ : SimpleCopyButton(
+ data: ref
+ .watch(pFrostResharingData)
+ .startResharedData!
+ .resharedStart,
+ ),
+ ),
+ const SizedBox(height: 12),
+ FrostQrDialogPopupButton(
+ data:
+ ref.watch(pFrostResharingData).startResharedData!.resharedStart,
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ CheckboxTextButton(
+ label: "I have verified that everyone has my encryption key",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: "Continue",
+ enabled: _userVerifyContinue,
+ onPressed: () {
+ ref.read(pFrostCreateCurrentStep.state).state = 4;
+ Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ },
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart
new file mode 100644
index 000000000..2eb7c8f65
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_4.dart
@@ -0,0 +1,248 @@
+import 'dart:ffi';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
+import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/services/frost.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
+import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
+import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.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';
+import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
+
+// was FinishResharingView
+class FrostReshareStep4 extends ConsumerStatefulWidget {
+ const FrostReshareStep4({super.key});
+
+ static const String routeName = "/frostReshareStep4";
+ static const String title = "Resharer completes";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep4State();
+}
+
+class _FrostReshareStep4State extends ConsumerState {
+ final List controllers = [];
+ final List focusNodes = [];
+
+ late final Map resharers;
+ late final String myName;
+ late final int? myResharerIndexIndex;
+ late final String? myResharerComplete;
+ late final bool amOutgoingParticipant;
+ late final bool amNewParticipant;
+
+ final List fieldIsEmptyFlags = [];
+
+ bool _userVerifyContinue = false;
+
+ bool _buttonLock = false;
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ if (amOutgoingParticipant) {
+ ref.read(pFrostResharingData).reset();
+ ref.read(pFrostScaffoldCanPopDesktop.notifier).state = true;
+ ref.read(pFrostScaffoldArgs)?.parentNav.popUntil(
+ ModalRoute.withName(
+ Util.isDesktop
+ ? DesktopWalletView.routeName
+ : WalletView.routeName,
+ ),
+ );
+ } else {
+ // collect resharer completes strings and insert my own at the correct index
+ final resharerCompletes = controllers.map((e) => e.text).toList();
+ if (myResharerIndexIndex != null && myResharerComplete != null) {
+ resharerCompletes.insert(myResharerIndexIndex!, myResharerComplete!);
+ }
+
+ final data = Frost.finishReshared(
+ prior: ref.read(pFrostResharingData).startResharedData!.prior.ref,
+ resharerCompletes: resharerCompletes,
+ );
+
+ ref.read(pFrostResharingData).newWalletData = data;
+
+ ref.read(pFrostCreateCurrentStep.state).state = 5;
+ await Navigator.of(context).pushNamed(
+ ref
+ .read(pFrostScaffoldArgs)!
+ .stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
+ .routeName,
+ );
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ @override
+ void initState() {
+ amNewParticipant =
+ ref.read(pFrostResharingData).startResharerData == null &&
+ ref.read(pFrostResharingData).incompleteWallet != null &&
+ ref.read(pFrostResharingData).incompleteWallet?.walletId ==
+ ref.read(pFrostScaffoldArgs)!.walletId!;
+
+ myName = ref.read(pFrostResharingData).myName!;
+
+ resharers = ref.read(pFrostResharingData).configData!.resharers;
+
+ if (amNewParticipant) {
+ myResharerComplete = null;
+ myResharerIndexIndex = null;
+ amOutgoingParticipant = false;
+ } else {
+ myResharerComplete = ref.read(pFrostResharingData).resharerComplete!;
+
+ final frostInfo = ref
+ .read(mainDBProvider)
+ .isar
+ .frostWalletInfo
+ .getByWalletIdSync(ref.read(pFrostScaffoldArgs)!.walletId!)!;
+ final myOldIndex =
+ frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!);
+
+ myResharerIndexIndex = resharers.values.toList().indexOf(myOldIndex);
+ if (myResharerIndexIndex! >= 0) {
+ // remove my name for now as we don't need a text field for it
+ resharers.remove(ref.read(pFrostResharingData).myName!);
+ }
+
+ amOutgoingParticipant = !ref
+ .read(pFrostResharingData)
+ .configData!
+ .newParticipants
+ .contains(ref.read(pFrostResharingData).myName!);
+ }
+
+ for (int i = 0; i < resharers.length; i++) {
+ controllers.add(TextEditingController());
+ focusNodes.add(FocusNode());
+ fieldIsEmptyFlags.add(true);
+ }
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ for (int i = 0; i < controllers.length; i++) {
+ controllers[i].dispose();
+ }
+ for (int i = 0; i < focusNodes.length; i++) {
+ focusNodes[i].dispose();
+ }
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ if (myResharerComplete != null)
+ DetailItem(
+ title: "My resharer complete",
+ detail: myResharerComplete!,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: myResharerComplete!,
+ )
+ : SimpleCopyButton(
+ data: myResharerComplete!,
+ ),
+ ),
+ if (myResharerComplete != null) const SizedBox(height: 12),
+ if (myResharerComplete != null)
+ FrostQrDialogPopupButton(
+ data: myResharerComplete!,
+ ),
+ if (!amOutgoingParticipant)
+ const SizedBox(
+ height: 16,
+ ),
+ if (!amOutgoingParticipant)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ for (int i = 0; i < resharers.length; i++)
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ child: FrostStepField(
+ controller: controllers[i],
+ focusNode: focusNodes[i],
+ showQrScanOption: true,
+ label: resharers.keys.elementAt(i),
+ hint: "Enter "
+ "${resharers.keys.elementAt(i)}"
+ "'s resharer",
+ onChanged: (_) {
+ setState(() {
+ fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 16,
+ ),
+ if (!amNewParticipant)
+ CheckboxTextButton(
+ label: "I have verified that everyone has my resharer complete",
+ onChanged: (value) {
+ setState(() {
+ _userVerifyContinue = value;
+ });
+ },
+ ),
+ if (!amNewParticipant)
+ const SizedBox(
+ height: 16,
+ ),
+ PrimaryButton(
+ label: amOutgoingParticipant ? "Done" : "Complete",
+ enabled: (amNewParticipant || _userVerifyContinue) &&
+ (amOutgoingParticipant ||
+ !fieldIsEmptyFlags.reduce((v, e) => v |= e)),
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart
new file mode 100644
index 000000000..2171f5ad8
--- /dev/null
+++ b/lib/pages/add_wallet_views/frost_ms/reshare/frost_reshare_step_5.dart
@@ -0,0 +1,221 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:stackwallet/frost_route_generator.dart';
+import 'package:stackwallet/pages/home_view/home_view.dart';
+import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
+import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
+import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
+import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
+import 'package:stackwallet/providers/db/main_db_provider.dart';
+import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
+import 'package:stackwallet/providers/global/node_service_provider.dart';
+import 'package:stackwallet/providers/global/prefs_provider.dart';
+import 'package:stackwallet/providers/global/secure_store_provider.dart';
+import 'package:stackwallet/providers/global/wallets_provider.dart';
+import 'package:stackwallet/utilities/logger.dart';
+import 'package:stackwallet/utilities/show_loading.dart';
+import 'package:stackwallet/utilities/text_styles.dart';
+import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
+import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/detail_item.dart';
+import 'package:stackwallet/widgets/stack_dialog.dart';
+
+// was VerifyUpdatedWalletView
+class FrostReshareStep5 extends ConsumerStatefulWidget {
+ const FrostReshareStep5({super.key});
+
+ static const String routeName = "/frostReshareStep5";
+ static const String title = "Verify";
+
+ @override
+ ConsumerState createState() => _FrostReshareStep5State();
+}
+
+class _FrostReshareStep5State extends ConsumerState {
+ late final String config;
+ late final String serializedKeys;
+ late final String reshareId;
+
+ late final bool isNew;
+
+ bool _buttonLock = false;
+ Future _onPressed() async {
+ if (_buttonLock) {
+ return;
+ }
+ _buttonLock = true;
+
+ try {
+ Exception? ex;
+
+ final BitcoinFrostWallet wallet;
+
+ if (isNew) {
+ wallet = await ref
+ .read(pFrostResharingData)
+ .incompleteWallet!
+ .toBitcoinFrostWallet(
+ mainDB: ref.read(mainDBProvider),
+ secureStorageInterface: ref.read(secureStoreProvider),
+ nodeService: ref.read(nodeServiceChangeNotifierProvider),
+ prefs: ref.read(prefsChangeNotifierProvider),
+ );
+
+ await wallet.info.setMnemonicVerified(
+ isar: ref.read(mainDBProvider).isar,
+ );
+
+ ref.read(pWallets).addWallet(wallet);
+ } else {
+ wallet = ref
+ .read(pWallets)
+ .getWallet(ref.read(pFrostScaffoldArgs)!.walletId!)
+ as BitcoinFrostWallet;
+ }
+
+ if (mounted) {
+ await showLoading(
+ whileFuture: wallet.updateWithResharedData(
+ serializedKeys: serializedKeys,
+ multisigConfig: config,
+ isNewWallet: isNew,
+ ),
+ context: context,
+ message: isNew ? "Creating wallet" : "Updating wallet data",
+ rootNavigator: true,
+ onException: (e) => ex = e,
+ );
+
+ if (ex != null) {
+ throw ex!;
+ }
+
+ if (mounted) {
+ ref.read(pFrostResharingData).reset();
+ ref.read(pFrostScaffoldCanPopDesktop.notifier).state = true;
+ ref.read(pFrostScaffoldArgs)?.parentNav.popUntil(
+ ModalRoute.withName(
+ _popUntilPath,
+ ),
+ );
+ }
+ }
+ } catch (e, s) {
+ Logging.instance.log(
+ "$e\n$s",
+ level: LogLevel.Fatal,
+ );
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: e.toString(),
+ desktopPopRootNavigator: Util.isDesktop,
+ ),
+ );
+ }
+ } finally {
+ _buttonLock = false;
+ }
+ }
+
+ String get _popUntilPath => isNew
+ ? Util.isDesktop
+ ? DesktopHomeView.routeName
+ : HomeView.routeName
+ : Util.isDesktop
+ ? DesktopWalletView.routeName
+ : WalletView.routeName;
+
+ @override
+ void initState() {
+ config = ref.read(pFrostResharingData).newWalletData!.multisigConfig;
+ serializedKeys =
+ ref.read(pFrostResharingData).newWalletData!.serializedKeys;
+ reshareId = ref.read(pFrostResharingData).newWalletData!.resharedId;
+
+ isNew = ref.read(pFrostResharingData).incompleteWallet != null &&
+ ref.read(pFrostResharingData).incompleteWallet!.walletId ==
+ ref.read(pFrostScaffoldArgs)!.walletId!;
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ children: [
+ Text(
+ "Ensure your reshare ID matches that of each other participant",
+ style: STextStyles.pageTitleH2(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ DetailItem(
+ title: "ID",
+ detail: reshareId,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: reshareId,
+ )
+ : SimpleCopyButton(
+ data: reshareId,
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ Text(
+ "Back up your keys and config",
+ style: STextStyles.pageTitleH2(context),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ DetailItem(
+ title: "Config",
+ detail: config,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: config,
+ )
+ : SimpleCopyButton(
+ data: config,
+ ),
+ ),
+ const SizedBox(
+ height: 12,
+ ),
+ DetailItem(
+ title: "Keys",
+ detail: serializedKeys,
+ button: Util.isDesktop
+ ? IconCopyButton(
+ data: serializedKeys,
+ )
+ : SimpleCopyButton(
+ data: serializedKeys,
+ ),
+ ),
+ if (!Util.isDesktop) const Spacer(),
+ const SizedBox(
+ height: 12,
+ ),
+ PrimaryButton(
+ label: "Confirm",
+ onPressed: _onPressed,
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart b/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart
index 08f36ebde..68c220c1f 100644
--- a/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart
+++ b/lib/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart
@@ -8,7 +8,6 @@ import 'package:frostdart/frostdart.dart' as frost;
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
-import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/global/node_service_provider.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
@@ -18,11 +17,11 @@ import 'package:stackwallet/services/frost.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
@@ -33,6 +32,7 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/frost_mascot.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
@@ -40,19 +40,17 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
-import 'package:stackwallet/pages/frost_mascot.dart';
-
class RestoreFrostMsWalletView extends ConsumerStatefulWidget {
const RestoreFrostMsWalletView({
super.key,
required this.walletName,
- required this.coin,
+ required this.frostCurrency,
});
static const String routeName = "/restoreFrostMsWalletView";
final String walletName;
- final Coin coin;
+ final FrostCurrency frostCurrency;
@override
ConsumerState createState() =>
@@ -77,7 +75,7 @@ class _RestoreFrostMsWalletViewState
final myName = participants[myNameIndex];
final info = WalletInfo.createNew(
- coin: widget.coin,
+ coin: widget.frostCurrency.coin,
name: widget.walletName,
);
@@ -132,7 +130,7 @@ class _RestoreFrostMsWalletViewState
whileFuture: _createWalletAndRecover(),
context: context,
message: "Restoring wallet...",
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
onException: (e) {
ex = e;
},
@@ -214,13 +212,15 @@ class _RestoreFrostMsWalletViewState
condition: Util.isDesktop,
builder: (child) => DesktopScaffold(
background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
+ appBar: const DesktopAppBar(
isCompactHeight: false,
leading: AppBarBackButton(),
+ // TODO: [prio=high] get rid of placeholder text??
trailing: FrostMascot(
title: 'Lorem ipsum',
- body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
- )
+ body:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
+ ),
),
body: SizedBox(
width: 480,
diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
index 7ddaaba3a..3cfeb6bbd 100644
--- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
+++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart
@@ -15,12 +15,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.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/import_new_frost_ms_wallet_view.dart';
+import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart';
import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart';
import 'package:stackwallet/pages/add_wallet_views/new_wallet_options/new_wallet_options_view.dart';
import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart';
import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart';
-import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart';
@@ -31,6 +30,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/name_generator.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
+import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
+import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
@@ -243,7 +244,7 @@ class _NameYourWalletViewState extends ConsumerState {
height: isDesktop ? 0 : 16,
),
Text(
- "Name your ${coin.prettyName} wallet",
+ "Name your ${coin.prettyName} ${coin.isFrost ? "multisig " : ""}wallet",
textAlign: TextAlign.center,
style: isDesktop
? STextStyles.desktopH2(context)
@@ -253,7 +254,7 @@ class _NameYourWalletViewState extends ConsumerState {
height: isDesktop ? 16 : 8,
),
Text(
- "Enter a label for your wallet (e.g. Savings)",
+ "Enter a label for your wallet (e.g. ${coin.isFrost ? "Multisig" : "Savings"})",
textAlign: TextAlign.center,
style: isDesktop
? STextStyles.desktopSubtitleH2(context)
@@ -394,7 +395,10 @@ class _NameYourWalletViewState extends ConsumerState {
RestoreFrostMsWalletView.routeName,
arguments: (
walletName: name,
- coin: coin,
+ // TODO: [prio=med] this will cause issues if frost is ever applied to other coins
+ frostCurrency: coin.isTestNet
+ ? BitcoinFrost(CryptoCurrencyNetwork.test)
+ : BitcoinFrost(CryptoCurrencyNetwork.main),
),
);
},
@@ -403,7 +407,7 @@ class _NameYourWalletViewState extends ConsumerState {
Column(
children: [
PrimaryButton(
- label: "Create config",
+ label: "Create new group",
enabled: _nextEnabled,
onPressed: () async {
final name = textEditingController.text;
@@ -412,7 +416,10 @@ class _NameYourWalletViewState extends ConsumerState {
CreateNewFrostMsWalletView.routeName,
arguments: (
walletName: name,
- coin: coin,
+ // TODO: [prio=med] this will cause issues if frost is ever applied to other coins
+ frostCurrency: coin.isTestNet
+ ? BitcoinFrost(CryptoCurrencyNetwork.test)
+ : BitcoinFrost(CryptoCurrencyNetwork.main),
),
);
},
@@ -421,38 +428,56 @@ class _NameYourWalletViewState extends ConsumerState {
height: 12,
),
SecondaryButton(
- label: "Import multisig config",
+ label: "Join group",
enabled: _nextEnabled,
onPressed: () async {
final name = textEditingController.text;
await Navigator.of(context).pushNamed(
- ImportNewFrostMsWalletView.routeName,
+ SelectNewFrostImportTypeView.routeName,
arguments: (
walletName: name,
- coin: coin,
- ),
- );
- },
- ),
- const SizedBox(
- height: 12,
- ),
- SecondaryButton(
- label: "Import resharer config",
- enabled: _nextEnabled,
- onPressed: () async {
- final name = textEditingController.text;
-
- await Navigator.of(context).pushNamed(
- NewImportResharerConfigView.routeName,
- arguments: (
- walletName: name,
- coin: coin,
+ // TODO: [prio=med] this will cause issues if frost is ever applied to other coins
+ frostCurrency: coin.isTestNet
+ ? BitcoinFrost(CryptoCurrencyNetwork.test)
+ : BitcoinFrost(CryptoCurrencyNetwork.main),
),
);
},
),
+ // SecondaryButton(
+ // label: "Import multisig config",
+ // enabled: _nextEnabled,
+ // onPressed: () async {
+ // final name = textEditingController.text;
+ //
+ // await Navigator.of(context).pushNamed(
+ // ImportNewFrostMsWalletView.routeName,
+ // arguments: (
+ // walletName: name,
+ // coin: coin,
+ // ),
+ // );
+ // },
+ // ),
+ // const SizedBox(
+ // height: 12,
+ // ),
+ // SecondaryButton(
+ // label: "Import resharer config",
+ // enabled: _nextEnabled,
+ // onPressed: () async {
+ // final name = textEditingController.text;
+ //
+ // await Navigator.of(context).pushNamed(
+ // NewImportResharerConfigView.routeName,
+ // arguments: (
+ // walletName: name,
+ // coin: coin,
+ // ),
+ // );
+ // },
+ // ),
],
),
if (!widget.coin.isFrost)
diff --git a/lib/pages/cashfusion/fusion_progress_view.dart b/lib/pages/cashfusion/fusion_progress_view.dart
index 746825d36..78bc9ccc7 100644
--- a/lib/pages/cashfusion/fusion_progress_view.dart
+++ b/lib/pages/cashfusion/fusion_progress_view.dart
@@ -79,7 +79,7 @@ class _FusionProgressViewState extends ConsumerState {
Future.delayed(const Duration(seconds: 2)),
]),
context: context,
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
message: "Stopping fusion",
);
diff --git a/lib/pages/monkey/monkey_view.dart b/lib/pages/monkey/monkey_view.dart
index a747011ee..9e8bdb090 100644
--- a/lib/pages/monkey/monkey_view.dart
+++ b/lib/pages/monkey/monkey_view.dart
@@ -349,7 +349,7 @@ class _MonkeyViewState extends ConsumerState {
),
]),
context: context,
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
message: "Saving MonKey svg",
onException: (e) {
didError = true;
@@ -402,7 +402,7 @@ class _MonkeyViewState extends ConsumerState {
const Duration(seconds: 2)),
]),
context: context,
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
message: "Downloading MonKey png",
onException: (e) {
didError = true;
@@ -500,7 +500,7 @@ class _MonkeyViewState extends ConsumerState {
Future.delayed(const Duration(seconds: 2)),
]),
context: context,
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
message: "Fetching MonKey",
subMessage: "We are fetching your MonKey",
onException: (e) {
diff --git a/lib/pages/ordinals/ordinal_details_view.dart b/lib/pages/ordinals/ordinal_details_view.dart
index d15523e10..590bca266 100644
--- a/lib/pages/ordinals/ordinal_details_view.dart
+++ b/lib/pages/ordinals/ordinal_details_view.dart
@@ -321,7 +321,7 @@ class _OrdinalImageGroup extends ConsumerWidget {
final filePath = await showLoading(
whileFuture: _savePngToFile(ref),
context: context,
- isDesktop: true,
+ rootNavigator: true,
message: "Saving ordinal image",
onException: (e) {
didError = true;
diff --git a/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart
deleted file mode 100644
index 149eb61d3..000000000
--- a/lib/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart
+++ /dev/null
@@ -1,404 +0,0 @@
-import 'package:barcode_scan2/barcode_scan2.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:qr_flutter/qr_flutter.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_continue_sign_config_view.dart';
-import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
-import 'package:stackwallet/widgets/stack_dialog.dart';
-import 'package:stackwallet/widgets/stack_text_field.dart';
-import 'package:stackwallet/widgets/textfield_icon_button.dart';
-
-class FrostAttemptSignConfigView extends ConsumerStatefulWidget {
- const FrostAttemptSignConfigView({
- super.key,
- required this.walletId,
- });
-
- static const String routeName = "/frostAttemptSignConfigView";
-
- final String walletId;
-
- @override
- ConsumerState createState() =>
- _FrostAttemptSignConfigViewState();
-}
-
-class _FrostAttemptSignConfigViewState
- extends ConsumerState {
- final List controllers = [];
- final List focusNodes = [];
-
- late final String myName;
- late final List participantsWithoutMe;
- late final String myPreprocess;
- late final int myIndex;
- late final int threshold;
-
- final List fieldIsEmptyFlags = [];
-
- bool hasEnoughPreprocesses() {
- // own preprocess is not included in controllers and must be set here
- int count = 1;
-
- for (final controller in controllers) {
- if (controller.text.isNotEmpty) {
- count++;
- }
- }
-
- return count >= threshold;
- }
-
- @override
- void initState() {
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
- final frostInfo = wallet.frostInfo;
-
- myName = frostInfo.myName;
- threshold = frostInfo.threshold;
- participantsWithoutMe = List.from(frostInfo.participants); // Copy so it isn't fixed-length.
- myIndex = participantsWithoutMe.indexOf(frostInfo.myName);
- myPreprocess = ref.read(pFrostAttemptSignData.state).state!.preprocess;
-
- participantsWithoutMe.removeAt(myIndex);
-
- for (int i = 0; i < participantsWithoutMe.length; i++) {
- controllers.add(TextEditingController());
- focusNodes.add(FocusNode());
- fieldIsEmptyFlags.add(true);
- }
- super.initState();
- }
-
- @override
- void dispose() {
- for (int i = 0; i < controllers.length; i++) {
- controllers[i].dispose();
- }
- for (int i = 0; i < focusNodes.length; i++) {
- focusNodes[i].dispose();
- }
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: const DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Preprocesses",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- height: 220,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data: myPreprocess,
- size: 220,
- backgroundColor:
- Theme.of(context).extension()!.background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const _Div(),
- DetailItem(
- title: "My name",
- detail: myName,
- ),
- const _Div(),
- DetailItem(
- title: "My preprocess",
- detail: myPreprocess,
- button: Util.isDesktop
- ? IconCopyButton(
- data: myPreprocess,
- )
- : SimpleCopyButton(
- data: myPreprocess,
- ),
- ),
- const _Div(),
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- for (int i = 0; i < participantsWithoutMe.length; i++)
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: Key("frostPreprocessesTextFieldKey_$i"),
- controller: controllers[i],
- focusNode: focusNodes[i],
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- onChanged: (_) {
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i].text.isEmpty;
- });
- },
- decoration: standardInputDecoration(
- "Enter ${participantsWithoutMe[i]}'s preprocess",
- focusNodes[i],
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
- ),
- suffixIcon: Padding(
- padding: fieldIsEmptyFlags[i]
- ? const EdgeInsets.only(right: 8)
- : const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceAround,
- children: [
- !fieldIsEmptyFlags[i]
- ? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears The Preprocess Field Input.",
- key: Key(
- "frostPreprocessesClearButtonKey_$i",
- ),
- onTap: () {
- controllers[i].text = "";
-
- setState(() {
- fieldIsEmptyFlags[i] = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Preprocess Field Input.",
- key: Key(
- "frostPreprocessesPasteButtonKey_$i",
- ),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- controllers[i].text =
- data.text!.trim();
- }
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i]
- .text
- .isEmpty;
- });
- },
- child: fieldIsEmptyFlags[i]
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (fieldIsEmptyFlags[i])
- TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera For Scanning QR Code.",
- key: Key(
- "frostPreprocessesScanQrButtonKey_$i",
- ),
- onTap: () async {
- try {
- if (FocusScope.of(context)
- .hasFocus) {
- FocusScope.of(context)
- .unfocus();
- await Future.delayed(
- const Duration(
- milliseconds: 75));
- }
-
- final qrResult =
- await BarcodeScanner.scan();
-
- controllers[i].text =
- qrResult.rawContent;
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i].text.isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Continue signing",
- enabled: hasEnoughPreprocesses(),
- onPressed: () async {
- // collect Preprocess strings (not including my own)
- final preprocesses = controllers.map((e) => e.text).toList();
-
- // collect participants who are involved in this transaction
- final List requiredParticipantsUnordered = [];
- for (int i = 0; i < participantsWithoutMe.length; i++) {
- if (preprocesses[i].isNotEmpty) {
- requiredParticipantsUnordered.add(participantsWithoutMe[i]);
- }
- }
- ref.read(pFrostSelectParticipantsUnordered.notifier).state =
- requiredParticipantsUnordered;
-
- // insert an empty string at my index
- preprocesses.insert(myIndex, "");
-
- try {
- ref.read(pFrostContinueSignData.notifier).state =
- Frost.continueSigning(
- machinePtr:
- ref.read(pFrostAttemptSignData.state).state!.machinePtr,
- preprocesses: preprocesses,
- );
-
- await Navigator.of(context).pushNamed(
- FrostContinueSignView.routeName,
- arguments: widget.walletId,
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Failed to continue signing",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
- },
- ),
- ],
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart b/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart
deleted file mode 100644
index 6478495c0..000000000
--- a/lib/pages/send_view/frost_ms/frost_complete_sign_view.dart
+++ /dev/null
@@ -1,206 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:qr_flutter/qr_flutter.dart';
-import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
-import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
-import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/show_loading.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-import 'package:stackwallet/widgets/stack_dialog.dart';
-
-class FrostCompleteSignView extends ConsumerStatefulWidget {
- const FrostCompleteSignView({
- super.key,
- required this.walletId,
- });
-
- static const String routeName = "/frostCompleteSignView";
-
- final String walletId;
-
- @override
- ConsumerState createState() =>
- _FrostCompleteSignViewState();
-}
-
-class _FrostCompleteSignViewState extends ConsumerState {
- bool _broadcastLock = false;
-
- @override
- Widget build(BuildContext context) {
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: const DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Preview transaction",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- height: 220,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data: ref.watch(pFrostTxData.state).state!.raw!,
- size: 220,
- backgroundColor:
- Theme.of(context).extension()!.background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const _Div(),
- DetailItem(
- title: "Raw transaction hex",
- detail: ref.watch(pFrostTxData.state).state!.raw!,
- button: Util.isDesktop
- ? IconCopyButton(
- data: ref.watch(pFrostTxData.state).state!.raw!,
- )
- : SimpleCopyButton(
- data: ref.watch(pFrostTxData.state).state!.raw!,
- ),
- ),
- const _Div(),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Broadcast Transaction",
- onPressed: () async {
- if (_broadcastLock) {
- return;
- }
- _broadcastLock = true;
-
- try {
- Exception? ex;
- final txData = await showLoading(
- whileFuture: ref
- .read(pWallets)
- .getWallet(widget.walletId)
- .confirmSend(
- txData: ref.read(pFrostTxData.state).state!,
- ),
- context: context,
- message: "Broadcasting transaction to network",
- isDesktop: Util.isDesktop,
- onException: (e) {
- ex = e;
- },
- );
-
- if (ex != null) {
- throw ex!;
- }
-
- if (mounted) {
- if (txData != null) {
- ref.read(pFrostTxData.state).state = txData;
- Navigator.of(context).popUntil(
- ModalRoute.withName(
- Util.isDesktop
- ? MyStackView.routeName
- : WalletView.routeName,
- ),
- );
- }
- }
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Broadcast error",
- message: e.toString(),
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- } finally {
- _broadcastLock = false;
- }
- },
- ),
- ],
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart
deleted file mode 100644
index 732fb2f82..000000000
--- a/lib/pages/send_view/frost_ms/frost_continue_sign_config_view.dart
+++ /dev/null
@@ -1,445 +0,0 @@
-import 'package:barcode_scan2/barcode_scan2.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:qr_flutter/qr_flutter.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_complete_sign_view.dart';
-import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
-import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
-import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
-import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
-import 'package:stackwallet/widgets/stack_dialog.dart';
-import 'package:stackwallet/widgets/stack_text_field.dart';
-import 'package:stackwallet/widgets/textfield_icon_button.dart';
-
-class FrostContinueSignView extends ConsumerStatefulWidget {
- const FrostContinueSignView({
- super.key,
- required this.walletId,
- });
-
- static const String routeName = "/frostContinueSignView";
-
- final String walletId;
-
- @override
- ConsumerState createState() =>
- _FrostContinueSignViewState();
-}
-
-class _FrostContinueSignViewState extends ConsumerState {
- final List controllers = [];
- final List focusNodes = [];
-
- late final String myName;
- late final List participantsWithoutMe;
- late final List participantsAll;
- late final String myShare;
- late final int myIndex;
-
- final List fieldIsEmptyFlags = [];
-
- @override
- void initState() {
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
-
- final frostInfo = wallet.frostInfo;
-
- myName = frostInfo.myName;
- participantsAll = frostInfo.participants;
- myIndex = frostInfo.participants.indexOf(frostInfo.myName);
- myShare = ref.read(pFrostContinueSignData.state).state!.share;
-
- participantsWithoutMe = frostInfo.participants
- .toSet()
- .intersection(
- ref.read(pFrostSelectParticipantsUnordered.state).state!.toSet())
- .toList();
-
- participantsWithoutMe.remove(myName);
-
- for (int i = 0; i < participantsWithoutMe.length; i++) {
- controllers.add(TextEditingController());
- focusNodes.add(FocusNode());
- fieldIsEmptyFlags.add(true);
- }
- super.initState();
- }
-
- @override
- void dispose() {
- for (int i = 0; i < controllers.length; i++) {
- controllers[i].dispose();
- }
- for (int i = 0; i < focusNodes.length; i++) {
- focusNodes[i].dispose();
- }
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async {
- await showDialog(
- context: context,
- builder: (_) => FrostInterruptionDialog(
- type: FrostInterruptionDialogType.transactionCreation,
- popUntilOnYesRouteName: Util.isDesktop
- ? DesktopWalletView.routeName
- : WalletView.routeName,
- ),
- );
- return false;
- },
- child: ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.transactionCreation,
- popUntilOnYesRouteName: DesktopWalletView.routeName,
- ),
- );
- },
- ),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () async {
- await showDialog(
- context: context,
- builder: (_) => const FrostInterruptionDialog(
- type: FrostInterruptionDialogType.transactionCreation,
- popUntilOnYesRouteName: WalletView.routeName,
- ),
- );
- },
- ),
- title: Text(
- "Shares",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- SizedBox(
- height: 220,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data: myShare,
- size: 220,
- backgroundColor: Theme.of(context)
- .extension()!
- .background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- const _Div(),
- DetailItem(
- title: "My name",
- detail: myName,
- ),
- const _Div(),
- DetailItem(
- title: "My shares",
- detail: myShare,
- button: Util.isDesktop
- ? IconCopyButton(
- data: myShare,
- )
- : SimpleCopyButton(
- data: myShare,
- ),
- ),
- const _Div(),
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- for (int i = 0; i < participantsWithoutMe.length; i++)
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: Key("frostSharesTextFieldKey_$i"),
- controller: controllers[i],
- focusNode: focusNodes[i],
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "Enter ${participantsWithoutMe[i]}'s share",
- focusNodes[i],
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
- ),
- suffixIcon: Padding(
- padding: fieldIsEmptyFlags[i]
- ? const EdgeInsets.only(right: 8)
- : const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceAround,
- children: [
- !fieldIsEmptyFlags[i]
- ? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears "
- "The Share Field Input.",
- key: Key(
- "frostSharesClearButtonKey_$i",
- ),
- onTap: () {
- controllers[i].text = "";
-
- setState(() {
- fieldIsEmptyFlags[i] = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From "
- "Clipboard To Share Field Input.",
- key: Key(
- "frostSharesPasteButtonKey_$i"),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- controllers[i].text =
- data.text!.trim();
- }
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i]
- .text
- .isEmpty;
- });
- },
- child: fieldIsEmptyFlags[i]
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (fieldIsEmptyFlags[i])
- TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera "
- "For Scanning QR Code.",
- key: Key(
- "frostSharesScanQrButtonKey_$i",
- ),
- onTap: () async {
- try {
- if (FocusScope.of(context)
- .hasFocus) {
- FocusScope.of(context)
- .unfocus();
- await Future.delayed(
- const Duration(
- milliseconds: 75));
- }
-
- final qrResult =
- await BarcodeScanner.scan();
-
- controllers[i].text =
- qrResult.rawContent;
-
- setState(() {
- fieldIsEmptyFlags[i] =
- controllers[i]
- .text
- .isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions "
- "while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- if (!Util.isDesktop) const Spacer(),
- const _Div(),
- PrimaryButton(
- label: "Complete signing",
- onPressed: () async {
- // check for empty shares
- if (controllers
- .map((e) => e.text.isEmpty)
- .reduce((value, element) => value |= element)) {
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Missing Shares",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
-
- // collect Share strings
- final sharesCollected =
- controllers.map((e) => e.text).toList();
-
- final List shares = [];
- for (final participant in participantsAll) {
- if (participantsWithoutMe.contains(participant)) {
- shares.add(sharesCollected[
- participantsWithoutMe.indexOf(participant)]);
- } else {
- shares.add("");
- }
- }
-
- try {
- final rawTx = Frost.completeSigning(
- machinePtr: ref
- .read(pFrostContinueSignData.state)
- .state!
- .machinePtr,
- shares: shares,
- );
-
- ref.read(pFrostTxData.state).state =
- ref.read(pFrostTxData.state).state!.copyWith(
- raw: rawTx,
- );
-
- await Navigator.of(context).pushNamed(
- FrostCompleteSignView.routeName,
- arguments: widget.walletId,
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Fatal,
- );
-
- return await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Failed to complete signing process",
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- }
- },
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _Div extends StatelessWidget {
- const _Div({super.key});
-
- @override
- Widget build(BuildContext context) {
- return const SizedBox(
- height: 12,
- );
- }
-}
diff --git a/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart
deleted file mode 100644
index bb2c129ca..000000000
--- a/lib/pages/send_view/frost_ms/frost_create_sign_config_view.dart
+++ /dev/null
@@ -1,185 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:qr_flutter/qr_flutter.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart';
-import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/detail_item.dart';
-
-class FrostCreateSignConfigView extends ConsumerStatefulWidget {
- const FrostCreateSignConfigView({
- super.key,
- required this.walletId,
- });
-
- static const String routeName = "/frostCreateSignConfigView";
-
- final String walletId;
-
- @override
- ConsumerState createState() =>
- _FrostCreateSignConfigViewState();
-}
-
-class _FrostCreateSignConfigViewState
- extends ConsumerState {
- bool _attemptSignLock = false;
-
- Future _attemptSign() async {
- if (_attemptSignLock) {
- return;
- }
-
- _attemptSignLock = true;
-
- try {
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
-
- final attemptSignRes = await wallet.frostAttemptSignConfig(
- config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
- );
-
- ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
-
- await Navigator.of(context).pushNamed(
- FrostAttemptSignConfigView.routeName,
- arguments: widget.walletId,
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Error,
- );
- } finally {
- _attemptSignLock = false;
- }
- }
-
- @override
- Widget build(BuildContext context) {
- double qrImageSize =
- Util.isDesktop ? 360 : MediaQuery.of(context).size.width - 32;
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: const DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- ),
- body: SingleChildScrollView(
- child: SizedBox(
- width: 600, // Was 480, may look better but overflows the bottom.
- child: child,
- ),
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Sign config",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- children: [
- if (!Util.isDesktop) const Spacer(),
- SizedBox(
- height: qrImageSize,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- QrImageView(
- data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
- size: qrImageSize,
- backgroundColor:
- Theme.of(context).extension()!.background,
- foregroundColor: Theme.of(context)
- .extension()!
- .accentColorDark,
- ),
- ],
- ),
- ),
- if (!Util.isDesktop)
- const SizedBox(
- height: 32,
- ),
- DetailItem(
- title: "Encoded config",
- detail: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
- button: Util.isDesktop
- ? IconCopyButton(
- data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
- )
- : SimpleCopyButton(
- data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
- ),
- ),
- SizedBox(
- height: Util.isDesktop ? 20 : 16,
- ),
- if (!Util.isDesktop)
- const Spacer(
- flex: 2,
- ),
- PrimaryButton(
- label: "Attempt sign",
- onPressed: () {
- _attemptSign();
- },
- ),
- const SizedBox(
- height: 16,
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart b/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart
deleted file mode 100644
index c89b6846a..000000000
--- a/lib/pages/send_view/frost_ms/frost_import_sign_config_view.dart
+++ /dev/null
@@ -1,332 +0,0 @@
-import 'package:barcode_scan2/barcode_scan2.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:isar/isar.dart';
-import 'package:stackwallet/models/isar/models/isar_models.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart';
-import 'package:stackwallet/providers/db/main_db_provider.dart';
-import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
-import 'package:stackwallet/providers/global/wallets_provider.dart';
-import 'package:stackwallet/services/frost.dart';
-import 'package:stackwallet/themes/stack_colors.dart';
-import 'package:stackwallet/utilities/constants.dart';
-import 'package:stackwallet/utilities/format.dart';
-import 'package:stackwallet/utilities/logger.dart';
-import 'package:stackwallet/utilities/text_styles.dart';
-import 'package:stackwallet/utilities/util.dart';
-import 'package:stackwallet/wallets/models/tx_data.dart';
-import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
-import 'package:stackwallet/widgets/background.dart';
-import 'package:stackwallet/widgets/conditional_parent.dart';
-import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
-import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
-import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
-import 'package:stackwallet/widgets/desktop/primary_button.dart';
-import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
-import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
-import 'package:stackwallet/widgets/stack_dialog.dart';
-import 'package:stackwallet/widgets/stack_text_field.dart';
-import 'package:stackwallet/widgets/textfield_icon_button.dart';
-
-class FrostImportSignConfigView extends ConsumerStatefulWidget {
- const FrostImportSignConfigView({
- super.key,
- required this.walletId,
- });
-
- static const String routeName = "/frostImportSignConfigView";
-
- final String walletId;
-
- @override
- ConsumerState createState() =>
- _FrostImportSignConfigViewState();
-}
-
-class _FrostImportSignConfigViewState
- extends ConsumerState {
- late final TextEditingController configFieldController;
- late final FocusNode configFocusNode;
-
- bool _configEmpty = true;
-
- bool _attemptSignLock = false;
-
- Future _attemptSign() async {
- if (_attemptSignLock) {
- return;
- }
-
- _attemptSignLock = true;
-
- try {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- }
-
- final config = configFieldController.text;
- final wallet =
- ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
-
- final data = Frost.extractDataFromSignConfig(
- signConfig: config,
- coin: wallet.cryptoCurrency,
- );
-
- final utxos = await ref
- .read(mainDBProvider)
- .getUTXOs(wallet.walletId)
- .filter()
- .anyOf(
- data.inputs,
- (q, e) => q
- .txidEqualTo(Format.uint8listToString(e.hash))
- .and()
- .valueEqualTo(e.value)
- .and()
- .voutEqualTo(e.vout))
- .findAll();
-
- // TODO add more data from 'data' and display to user ?
- ref.read(pFrostTxData.notifier).state = TxData(
- frostMSConfig: config,
- recipients: data.recipients
- .map((e) => (address: e.address, amount: e.amount, isChange: false))
- .toList(),
- utxos: utxos.toSet(),
- );
-
- final attemptSignRes = await wallet.frostAttemptSignConfig(
- config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
- );
-
- ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
-
- await Navigator.of(context).pushNamed(
- FrostAttemptSignConfigView.routeName,
- arguments: widget.walletId,
- );
- } catch (e, s) {
- Logging.instance.log(
- "$e\n$s",
- level: LogLevel.Error,
- );
- await showDialog(
- context: context,
- builder: (_) => StackOkDialog(
- title: "Import and attempt sign config failed",
- message: e.toString(),
- desktopPopRootNavigator: Util.isDesktop,
- ),
- );
- } finally {
- _attemptSignLock = false;
- }
- }
-
- @override
- void initState() {
- configFieldController = TextEditingController();
- configFocusNode = FocusNode();
- super.initState();
- }
-
- @override
- void dispose() {
- configFieldController.dispose();
- configFocusNode.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return ConditionalParent(
- condition: Util.isDesktop,
- builder: (child) => DesktopScaffold(
- background: Theme.of(context).extension()!.background,
- appBar: const DesktopAppBar(
- isCompactHeight: false,
- leading: AppBarBackButton(),
- ),
- body: SizedBox(
- width: 480,
- child: child,
- ),
- ),
- child: ConditionalParent(
- condition: !Util.isDesktop,
- builder: (child) => Background(
- child: Scaffold(
- backgroundColor:
- Theme.of(context).extension()!.background,
- appBar: AppBar(
- leading: AppBarBackButton(
- onPressed: () {
- Navigator.of(context).pop();
- },
- ),
- title: Text(
- "Import FROST sign config",
- style: STextStyles.navBarTitle(context),
- ),
- ),
- body: SafeArea(
- child: LayoutBuilder(
- builder: (context, constraints) {
- return SingleChildScrollView(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: IntrinsicHeight(
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: child,
- ),
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const SizedBox(
- height: 16,
- ),
- ClipRRect(
- borderRadius: BorderRadius.circular(
- Constants.size.circularBorderRadius,
- ),
- child: TextField(
- key: const Key("frConfigTextFieldKey"),
- controller: configFieldController,
- onChanged: (_) {
- setState(() {
- _configEmpty = configFieldController.text.isEmpty;
- });
- },
- focusNode: configFocusNode,
- readOnly: false,
- autocorrect: false,
- enableSuggestions: false,
- style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "Enter config",
- configFocusNode,
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.only(
- left: 16,
- top: 6,
- bottom: 8,
- right: 5,
- ),
- suffixIcon: Padding(
- padding: _configEmpty
- ? const EdgeInsets.only(right: 8)
- : const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: [
- !_configEmpty
- ? TextFieldIconButton(
- semanticsLabel:
- "Clear Button. Clears The Config Field.",
- key: const Key("frConfigClearButtonKey"),
- onTap: () {
- configFieldController.text = "";
-
- setState(() {
- _configEmpty = true;
- });
- },
- child: const XIcon(),
- )
- : TextFieldIconButton(
- semanticsLabel:
- "Paste Button. Pastes From Clipboard To Config Field Input.",
- key: const Key("frConfigPasteButtonKey"),
- onTap: () async {
- final ClipboardData? data =
- await Clipboard.getData(
- Clipboard.kTextPlain);
- if (data?.text != null &&
- data!.text!.isNotEmpty) {
- configFieldController.text =
- data.text!.trim();
- }
-
- setState(() {
- _configEmpty =
- configFieldController.text.isEmpty;
- });
- },
- child: _configEmpty
- ? const ClipboardIcon()
- : const XIcon(),
- ),
- if (_configEmpty)
- TextFieldIconButton(
- semanticsLabel:
- "Scan QR Button. Opens Camera For Scanning QR Code.",
- key: const Key("frConfigScanQrButtonKey"),
- onTap: () async {
- try {
- if (FocusScope.of(context).hasFocus) {
- FocusScope.of(context).unfocus();
- await Future.delayed(
- const Duration(milliseconds: 75));
- }
-
- final qrResult = await BarcodeScanner.scan();
-
- configFieldController.text =
- qrResult.rawContent;
-
- setState(() {
- _configEmpty =
- configFieldController.text.isEmpty;
- });
- } on PlatformException catch (e, s) {
- Logging.instance.log(
- "Failed to get camera permissions while trying to scan qr code: $e\n$s",
- level: LogLevel.Warning,
- );
- }
- },
- child: const QrCodeIcon(),
- )
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(
- height: 16,
- ),
- if (!Util.isDesktop) const Spacer(),
- const SizedBox(
- height: 16,
- ),
- PrimaryButton(
- label: "Start signing",
- enabled: !_configEmpty,
- onPressed: () {
- _attemptSign();
- },
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/pages/send_view/frost_ms/frost_send_view.dart b/lib/pages/send_view/frost_ms/frost_send_view.dart
index 5f04bb346..133d0afd4 100644
--- a/lib/pages/send_view/frost_ms/frost_send_view.dart
+++ b/lib/pages/send_view/frost_ms/frost_send_view.dart
@@ -14,10 +14,9 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
+import 'package:stackwallet/frost_route_generator.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_create_sign_config_view.dart';
-import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart';
import 'package:stackwallet/pages/send_view/frost_ms/recipient.dart';
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
import 'package:stackwallet/providers/providers.dart';
@@ -25,7 +24,6 @@ import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
-import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/show_loading.dart';
@@ -39,7 +37,10 @@ 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/blue_text_button.dart';
+import 'package:stackwallet/widgets/desktop/primary_button.dart';
+import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
+import 'package:stackwallet/widgets/frost_scaffold.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
@@ -49,10 +50,10 @@ import 'package:tuple/tuple.dart';
class FrostSendView extends ConsumerStatefulWidget {
const FrostSendView({
- Key? key,
+ super.key,
required this.walletId,
required this.coin,
- }) : super(key: key);
+ });
static const String routeName = "/frostSendView";
@@ -115,19 +116,33 @@ class _FrostSendViewState extends ConsumerState {
whileFuture: _loadingFuture(),
context: context,
message: "Generating sign config",
- isDesktop: Util.isDesktop,
+ rootNavigator: Util.isDesktop,
onException: (e) {
throw e;
},
);
}
+ final wallet =
+ ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet;
+
if (mounted && txData != null) {
ref.read(pFrostTxData.notifier).state = txData;
+ ref.read(pFrostScaffoldArgs.state).state = (
+ info: (
+ walletName: wallet.info.name,
+ frostCurrency: wallet.cryptoCurrency,
+ ),
+ walletId: walletId,
+ stepRoutes: FrostRouteGenerator.sendFrostTxStepRoutes,
+ parentNav: Navigator.of(context),
+ frostInterruptionDialogType:
+ FrostInterruptionDialogType.transactionCreation,
+ );
+
await Navigator.of(context).pushNamed(
- FrostCreateSignConfigView.routeName,
- arguments: widget.walletId,
+ FrostStepScaffold.routeName,
);
}
} catch (e) {
@@ -168,16 +183,24 @@ class _FrostSendViewState extends ConsumerState {
int customFeeRate = 1;
- void _validateRecipientFormStates() {
+ bool _buttonEnabled = false;
+
+ bool _validateRecipientFormStatesHelper() {
for (final i in recipientWidgetIndexes) {
- final state = ref.read(pRecipient(i).state).state;
- if (state?.amount == null || state?.address == null) {
- ref.read(previewTxButtonStateProvider.notifier).state = false;
- return;
+ final state = ref.read(pRecipient(i));
+ if (state?.amount == null ||
+ state?.address == null ||
+ state!.address.isEmpty) {
+ return false;
}
}
- ref.read(previewTxButtonStateProvider.notifier).state = true;
- return;
+ return true;
+ }
+
+ void _validateRecipientFormStates() {
+ setState(() {
+ _buttonEnabled = _validateRecipientFormStatesHelper();
+ });
}
@override
@@ -223,7 +246,7 @@ class _FrostSendViewState extends ConsumerState {
FocusScope.of(context).unfocus();
await Future.delayed(const Duration(milliseconds: 50));
}
- if (mounted) {
+ if (context.mounted) {
Navigator.of(context).pop();
}
},
@@ -232,40 +255,6 @@ class _FrostSendViewState extends ConsumerState {
"Send ${coin.ticker}",
style: STextStyles.navBarTitle(context),
),
- actions: [
- Padding(
- padding: const EdgeInsets.only(
- top: 10,
- bottom: 10,
- right: 10,
- ),
- child: AspectRatio(
- aspectRatio: 1,
- child: AppBarIconButton(
- semanticsLabel: "Import sign config Button.",
- key: const Key("importSignConfigButtonKey"),
- size: 36,
- shadows: const [],
- color:
- Theme.of(context).extension()!.background,
- icon: SvgPicture.asset(
- Assets.svg.circlePlus,
- color: Theme.of(context)
- .extension()!
- .accentColorDark,
- width: 20,
- height: 20,
- ),
- onPressed: () {
- Navigator.of(context).pushNamed(
- FrostImportSignConfigView.routeName,
- arguments: walletId,
- );
- },
- ),
- ),
- ),
- ],
),
body: LayoutBuilder(
builder: (builderContext, constraints) {
@@ -349,14 +338,7 @@ class _FrostSendViewState extends ConsumerState {
)
: const Spacer(),
GestureDetector(
- onTap: () {
- // cryptoAmountController.text = ref
- // .read(pAmountFormatter(coin))
- // .format(
- // _cachedBalance!,
- // withUnitName: false,
- // );
- },
+ onTap: () {},
child: Container(
color: Colors.transparent,
child: Column(
@@ -372,24 +354,6 @@ class _FrostSendViewState extends ConsumerState {
),
textAlign: TextAlign.right,
),
- // Text(
- // "${(manager.balance.spendable.decimal * ref.watch(
- // priceAnd24hChangeNotifierProvider.select(
- // (value) => value.getPrice(coin).item1,
- // ),
- // )).toAmount(
- // fractionDigits: 2,
- // ).fiatString(
- // locale: locale,
- // )} ${ref.watch(
- // prefsChangeNotifierProvider
- // .select((value) => value.currency),
- // )}",
- // style: STextStyles.subtitle(context).copyWith(
- // fontSize: 8,
- // ),
- // textAlign: TextAlign.right,
- // )
],
),
),
@@ -398,30 +362,8 @@ class _FrostSendViewState extends ConsumerState {
),
),
),
- const SizedBox(
- height: 16,
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- "Recipients",
- style: STextStyles.smallMed12(context),
- textAlign: TextAlign.left,
- ),
- CustomTextButton(
- text: "Add",
- onTap: () {
- // used for tracking recipient forms
- _greatestWidgetIndex++;
- recipientWidgetIndexes.add(_greatestWidgetIndex);
- setState(() {});
- },
- ),
- ],
- ),
- const SizedBox(
- height: 8,
+ SizedBox(
+ height: recipientWidgetIndexes.length > 1 ? 8 : 16,
),
Column(
children: [
@@ -437,6 +379,7 @@ class _FrostSendViewState extends ConsumerState {
"recipientKey_${recipientWidgetIndexes[i]}",
),
index: recipientWidgetIndexes[i],
+ displayNumber: i + 1,
coin: coin,
onChanged: () {
_validateRecipientFormStates();
@@ -444,13 +387,46 @@ class _FrostSendViewState extends ConsumerState {
remove: i == 0 && recipientWidgetIndexes.length == 1
? null
: () {
+ ref
+ .read(pRecipient(recipientWidgetIndexes[i])
+ .notifier)
+ .state = null;
recipientWidgetIndexes.removeAt(i);
setState(() {});
+ _validateRecipientFormStates();
},
+ addAnotherRecipientTapped: () {
+ // used for tracking recipient forms
+ _greatestWidgetIndex++;
+ recipientWidgetIndexes.add(_greatestWidgetIndex);
+ setState(() {});
+ _validateRecipientFormStates();
+ },
+ sendAllTapped: () {
+ return ref.read(pAmountFormatter(coin)).format(
+ ref.read(pWalletBalance(walletId)).spendable,
+ withUnitName: false,
+ );
+ },
),
),
],
),
+ if (recipientWidgetIndexes.length > 1)
+ const SizedBox(
+ height: 12,
+ ),
+ if (recipientWidgetIndexes.length > 1)
+ SecondaryButton(
+ width: double.infinity,
+ label: "Add recipient",
+ onPressed: () {
+ // used for tracking recipient forms
+ _greatestWidgetIndex++;
+ recipientWidgetIndexes.add(_greatestWidgetIndex);
+ setState(() {});
+ },
+ ),
if (showCoinControl)
const SizedBox(
height: 8,
@@ -481,12 +457,6 @@ class _FrostSendViewState extends ConsumerState {
}
if (mounted) {
- // finally spendable = ref
- // .read(walletsChangeNotifierProvider)
- // .getManager(widget.walletId)
- // .balance
- // .spendable;
-
// TODO: [prio=high] make sure this coincontrol works correctly
Amount? amount;
@@ -571,6 +541,7 @@ class _FrostSendViewState extends ConsumerState {
),
child: FeeSlider(
coin: coin,
+ showWU: true,
onSatVByteChanged: (rate) {
customFeeRate = rate;
},
@@ -584,21 +555,10 @@ class _FrostSendViewState extends ConsumerState {
const SizedBox(
height: 12,
),
- TextButton(
- onPressed: ref.watch(previewTxButtonStateProvider.state).state
- ? _createSignConfig
- : null,
- style: ref.watch(previewTxButtonStateProvider.state).state
- ? Theme.of(context)
- .extension