mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-02 03:06:29 +00:00
commit
52a884deeb
98 changed files with 7077 additions and 7538 deletions
10
assets/svg/swap2.svg
Normal file
10
assets/svg/swap2.svg
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_9616_26154)">
|
||||||
|
<path d="M11.2188 3.90625H3.625C2.38236 3.90625 1.375 4.91361 1.375 6.15625M11.2188 3.90625L8.96875 1.375M11.2188 3.90625L8.96875 6.4375M2.78125 10.375L10.375 10.375C11.6176 10.375 12.625 9.36764 12.625 8.125M2.78125 10.375L5.03125 12.9062M2.78125 10.375L5.03125 7.84375" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_9616_26154">
|
||||||
|
<rect width="13.5" height="13.5" fill="white" transform="translate(0.25 0.25)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 643 B |
|
@ -51,12 +51,12 @@ class ClientManager {
|
||||||
|
|
||||||
if (_map[key] == null) {
|
if (_map[key] == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"No managed ElectrumClient for $cryptoCurrency found.",
|
"No managed ElectrumClient for $key found.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_heightCompleters[key] == null) {
|
if (_heightCompleters[key] == null) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
"No managed _heightCompleters for $cryptoCurrency found.",
|
"No managed _heightCompleters for $key found.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
274
lib/frost_route_generator.dart
Normal file
274
lib/frost_route_generator.dart
Normal file
|
@ -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<FrostStepRoute> stepRoutes,
|
||||||
|
FrostInterruptionDialogType frostInterruptionDialogType,
|
||||||
|
NavigatorState parentNav,
|
||||||
|
})?>((ref) => null);
|
||||||
|
|
||||||
|
abstract class FrostRouteGenerator {
|
||||||
|
static const bool useMaterialPageRoute = true;
|
||||||
|
|
||||||
|
static const List<FrostStepRoute> 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<FrostStepRoute> 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<FrostStepRoute> 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<FrostStepRoute> 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<FrostStepRoute> 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<FrostStepRoute> sendFrostTxStepRoutes = [
|
||||||
|
(routeName: FrostSendStep1a.routeName, title: FrostSendStep1a.title),
|
||||||
|
(routeName: FrostSendStep2.routeName, title: FrostSendStep2.title),
|
||||||
|
(routeName: FrostSendStep3.routeName, title: FrostSendStep3.title),
|
||||||
|
(routeName: FrostSendStep4.routeName, title: FrostSendStep4.title),
|
||||||
|
];
|
||||||
|
|
||||||
|
static const List<FrostStepRoute> signFrostTxStepRoutes = [
|
||||||
|
(routeName: FrostSendStep1b.routeName, title: FrostSendStep1b.title),
|
||||||
|
(routeName: FrostSendStep2.routeName, title: FrostSendStep2.title),
|
||||||
|
(routeName: FrostSendStep3.routeName, title: FrostSendStep3.title),
|
||||||
|
(routeName: FrostSendStep4.routeName, title: FrostSendStep4.title),
|
||||||
|
];
|
||||||
|
|
||||||
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
|
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<dynamic> _routeError(String message) {
|
||||||
|
return RouteGenerator.getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => Placeholder(
|
||||||
|
child: Center(
|
||||||
|
child: Text(message),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
class AddWalletView extends ConsumerStatefulWidget {
|
class AddWalletView extends ConsumerStatefulWidget {
|
||||||
const AddWalletView({Key? key}) : super(key: key);
|
const AddWalletView({super.key});
|
||||||
|
|
||||||
static const routeName = "/addWallet";
|
static const routeName = "/addWallet";
|
||||||
|
|
||||||
|
@ -134,11 +134,6 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
||||||
_coins.remove(Coin.wownero);
|
_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.
|
// Remove Solana from the list of coins based on our frostEnabled preference.
|
||||||
if (!ref.read(prefsChangeNotifierProvider).solanaEnabled) {
|
if (!ref.read(prefsChangeNotifierProvider).solanaEnabled) {
|
||||||
_coins.remove(Coin.solana);
|
_coins.remove(Coin.solana);
|
||||||
|
@ -147,10 +142,6 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
||||||
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
||||||
|
|
||||||
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {
|
if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) {
|
||||||
if (!ref.read(prefsChangeNotifierProvider).frostEnabled) {
|
|
||||||
_coinsTestnet.remove(Coin.bitcoinFrostTestNet);
|
|
||||||
}
|
|
||||||
|
|
||||||
coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
|
coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<ConfirmNewFrostMSWalletCreationView> createState() =>
|
|
||||||
_ConfirmNewFrostMSWalletCreationViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConfirmNewFrostMSWalletCreationViewState
|
|
||||||
extends ConsumerState<ConfirmNewFrostMSWalletCreationView> {
|
|
||||||
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<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<dynamic>(
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +1,37 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/pages/frost_mascot.dart';
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/share_new_multisig_config_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
import 'package:stackwallet/services/frost.dart';
|
import 'package:stackwallet/services/frost.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.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/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.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/background.dart';
|
||||||
import 'package:stackwallet/widgets/conditional_parent.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/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_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/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';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
class CreateNewFrostMsWalletView extends ConsumerStatefulWidget {
|
class CreateNewFrostMsWalletView extends ConsumerStatefulWidget {
|
||||||
const CreateNewFrostMsWalletView({
|
const CreateNewFrostMsWalletView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.walletName,
|
required this.walletName,
|
||||||
required this.coin,
|
required this.frostCurrency,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const String routeName = "/createNewFrostMsWalletView";
|
static const String routeName = "/createNewFrostMsWalletView";
|
||||||
|
|
||||||
final String walletName;
|
final String walletName;
|
||||||
final Coin coin;
|
final FrostCurrency frostCurrency;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<CreateNewFrostMsWalletView> createState() =>
|
ConsumerState<CreateNewFrostMsWalletView> createState() =>
|
||||||
|
@ -67,13 +71,14 @@ class _NewFrostMsWalletViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasEmptyParticipants = controllers
|
final hasEmptyParticipants = controllers
|
||||||
.map((e) => e.text.isEmpty)
|
.map((e) => e.text.trim().isEmpty)
|
||||||
.reduce((value, element) => value |= element);
|
.reduce((value, element) => value |= element);
|
||||||
if (hasEmptyParticipants) {
|
if (hasEmptyParticipants) {
|
||||||
return "Participants must not be empty";
|
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";
|
return "Duplicate participant name found";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +107,31 @@ class _NewFrostMsWalletViewState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showWhatIsThresholdDialog() {
|
||||||
|
showDialog<void>(
|
||||||
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_thresholdController.dispose();
|
_thresholdController.dispose();
|
||||||
|
@ -118,12 +148,14 @@ class _NewFrostMsWalletViewState
|
||||||
condition: Util.isDesktop,
|
condition: Util.isDesktop,
|
||||||
builder: (child) => DesktopScaffold(
|
builder: (child) => DesktopScaffold(
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
background: Theme.of(context).extension<StackColors>()!.background,
|
||||||
appBar: DesktopAppBar(
|
appBar: const DesktopAppBar(
|
||||||
isCompactHeight: false,
|
isCompactHeight: false,
|
||||||
leading: AppBarBackButton(),
|
leading: AppBarBackButton(),
|
||||||
|
// TODO: [prio=high] get rid of placeholder text??
|
||||||
trailing: FrostMascot(
|
trailing: FrostMascot(
|
||||||
title: 'Lorem ipsum',
|
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(
|
body: SizedBox(
|
||||||
|
@ -144,7 +176,7 @@ class _NewFrostMsWalletViewState
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"New FROST multisig config",
|
"Create new group",
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -172,9 +204,21 @@ class _NewFrostMsWalletViewState
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
"Threshold",
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
style: STextStyles.label(context),
|
children: [
|
||||||
|
Text(
|
||||||
|
"Threshold",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
text: "What is a threshold?",
|
||||||
|
onTap: _showWhatIsThresholdDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
|
@ -183,22 +227,53 @@ class _NewFrostMsWalletViewState
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
controller: _thresholdController,
|
controller: _thresholdController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter number of signatures",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Number of participants",
|
"Number of participants",
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
TextField(
|
Column(
|
||||||
keyboardType: TextInputType.number,
|
mainAxisSize: MainAxisSize.min,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
controller: _participantsController,
|
children: [
|
||||||
onChanged: _participantsCountChanged,
|
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(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
|
@ -206,24 +281,75 @@ class _NewFrostMsWalletViewState
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
"My name",
|
"My name",
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
TextField(
|
Column(
|
||||||
controller: controllers.first,
|
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)
|
if (controllers.length > 1)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
if (controllers.length > 1)
|
if (controllers.length > 1)
|
||||||
Text(
|
Column(
|
||||||
"Remaining participants",
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: STextStyles.label(context),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Remaining participants",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.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)
|
if (controllers.length > 1)
|
||||||
Column(
|
Column(
|
||||||
|
@ -235,6 +361,10 @@ class _NewFrostMsWalletViewState
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controllers[i],
|
controller: controllers[i],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter name",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -244,7 +374,7 @@ class _NewFrostMsWalletViewState
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Generate",
|
label: "Create new group",
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (FocusScope.of(context).hasFocus) {
|
if (FocusScope.of(context).hasFocus) {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
|
@ -263,20 +393,29 @@ class _NewFrostMsWalletViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
final config = Frost.createMultisigConfig(
|
final config = Frost.createMultisigConfig(
|
||||||
name: controllers.first.text,
|
name: controllers.first.text.trim(),
|
||||||
threshold: int.parse(_thresholdController.text),
|
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;
|
ref.read(pFrostMultisigConfig.notifier).state = config;
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
ShareNewMultisigConfigView.routeName,
|
info: (
|
||||||
arguments: (
|
|
||||||
walletName: widget.walletName,
|
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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -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<FrostShareCommitmentsView> createState() =>
|
|
||||||
_FrostShareCommitmentsViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostShareCommitmentsViewState
|
|
||||||
extends ConsumerState<FrostShareCommitmentsView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<String> participants;
|
|
||||||
late final String myCommitment;
|
|
||||||
late final int myIndex;
|
|
||||||
|
|
||||||
final List<bool> 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<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.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<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "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<void>(
|
|
||||||
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<void>(
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FrostShareSharesView> createState() =>
|
|
||||||
_FrostShareSharesViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostShareSharesViewState extends ConsumerState<FrostShareSharesView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<String> participants;
|
|
||||||
late final String myShare;
|
|
||||||
late final int myIndex;
|
|
||||||
|
|
||||||
final List<bool> 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<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
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<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.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<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Generate",
|
|
||||||
onPressed: () async {
|
|
||||||
// check for empty commitments
|
|
||||||
if (controllers
|
|
||||||
.map((e) => e.text.isEmpty)
|
|
||||||
.reduce((value, element) => value |= element)) {
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Missing shares",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect 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<void>(
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<ImportNewFrostMsWalletView> createState() =>
|
|
||||||
_ImportNewFrostMsWalletViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ImportNewFrostMsWalletViewState
|
|
||||||
extends ConsumerState<ImportNewFrostMsWalletView> {
|
|
||||||
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<StackColors>()!.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<StackColors>()!.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<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult = await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
configFieldController.text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Start 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<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Invalid config",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Frost.getParticipants(multisigConfig: config)
|
|
||||||
.contains(myNameFieldController.text)) {
|
|
||||||
return await showDialog<void>(
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SelectNewFrostImportTypeView> createState() =>
|
||||||
|
_SelectNewFrostImportTypeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectNewFrostImportTypeViewState
|
||||||
|
extends ConsumerState<SelectNewFrostImportTypeView> {
|
||||||
|
_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<StackColors>()!.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<StackColors>()!
|
||||||
|
.topNavIconPrimary,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const _FrostJoinInfoDialog(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.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<StackColors>()!
|
||||||
|
.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<StackColors>()!
|
||||||
|
.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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ShareNewMultisigConfigView> createState() =>
|
|
||||||
_ShareNewMultisigConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShareNewMultisigConfigViewState
|
|
||||||
extends ConsumerState<ShareNewMultisigConfigView> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.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<StackColors>()!.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FrostCreateStep1a> createState() => _FrostCreateStep1aState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep1aState extends ConsumerState<FrostCreateStep1a> {
|
||||||
|
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<void>(
|
||||||
|
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<StackColors>()!.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<StackColors>()!.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<StackColors>()!
|
||||||
|
.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<StackColors>()!.background,
|
||||||
|
foregroundColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostCreateStep1b> createState() => _FrostCreateStep1bState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep1bState extends ConsumerState<FrostCreateStep1b> {
|
||||||
|
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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Invalid config",
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Frost.getParticipants(multisigConfig: config)
|
||||||
|
.contains(myNameFieldController.text)) {
|
||||||
|
return await showDialog<void>(
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostCreateStep2> createState() => _FrostCreateStep2State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep2State extends ConsumerState<FrostCreateStep2> {
|
||||||
|
static const info = [
|
||||||
|
"Share your commitment with other group members.",
|
||||||
|
"Enter their commitments into the corresponding fields.",
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final List<String> participants;
|
||||||
|
late final String myCommitment;
|
||||||
|
late final int myIndex;
|
||||||
|
|
||||||
|
final List<bool> 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<void>(
|
||||||
|
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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Failed to generate shares",
|
||||||
|
message: e.toString(),
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostCreateStep3> createState() => _FrostCreateStep3State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep3State extends ConsumerState<FrostCreateStep3> {
|
||||||
|
static const info = [
|
||||||
|
"Send your share to other group members.",
|
||||||
|
"Enter their shares into the corresponding fields.",
|
||||||
|
];
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final List<String> participants;
|
||||||
|
late final String myShare;
|
||||||
|
late final int myIndex;
|
||||||
|
|
||||||
|
final List<bool> 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<void>(
|
||||||
|
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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Failed to complete key generation",
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostCreateStep4> createState() => _FrostCreateStep4State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep4State extends ConsumerState<FrostCreateStep4> {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostCreateStep5> createState() => _FrostCreateStep5State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep5State extends ConsumerState<FrostCreateStep5> {
|
||||||
|
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<StackColors>()!.warningBackground,
|
||||||
|
child: Text(
|
||||||
|
_warning,
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.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<dynamic>(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep1a> createState() => _FrostReshareStep1aState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep1aState extends ConsumerState<FrostReshareStep1a> {
|
||||||
|
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<void> _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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: e.toString(),
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_buttonLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showParticipantsDialog() {
|
||||||
|
final participants =
|
||||||
|
ref.read(pFrostResharingData).configData!.newParticipants;
|
||||||
|
|
||||||
|
showDialog<void>(
|
||||||
|
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<StackColors>()!.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<StackColors>()!.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<StackColors>()!
|
||||||
|
.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<StackColors>()!.background,
|
||||||
|
foregroundColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep1b> createState() => _FrostReshareStep1bState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep1bState extends ConsumerState<FrostReshareStep1b> {
|
||||||
|
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<void> _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<void>(
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep1c> createState() => _FrostReshareStep1cState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep1cState extends ConsumerState<FrostReshareStep1c> {
|
||||||
|
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<IncompleteFrostWallet> _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<void>(
|
||||||
|
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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: e.toString(),
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_buttonLock = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep2abd> createState() =>
|
||||||
|
_FrostReshareStep2abdState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep2abdState extends ConsumerState<FrostReshareStep2abd> {
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final Map<String, int> resharers;
|
||||||
|
late final int myResharerIndexIndex;
|
||||||
|
late final String myResharerStart;
|
||||||
|
late final bool amOutgoingParticipant;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _buttonLock = false;
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
|
Future<void> _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<void>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep2c> createState() => _FrostReshareStep2cState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep2cState extends ConsumerState<FrostReshareStep2c> {
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final Map<String, int> resharers;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _buttonLock = false;
|
||||||
|
Future<void> _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<void>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep3abd> createState() =>
|
||||||
|
_FrostReshareStep3abdState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep3abdState extends ConsumerState<FrostReshareStep3abd> {
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final List<String> newParticipants;
|
||||||
|
late final int myIndex;
|
||||||
|
late final String? myEncryptionKey;
|
||||||
|
late final bool amOutgoingParticipant;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
|
bool _buttonLock = false;
|
||||||
|
Future<void> _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<void>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep3c> createState() => _FrostReshareStep3cState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep3cState extends ConsumerState<FrostReshareStep3c> {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep4> createState() => _FrostReshareStep4State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep4State extends ConsumerState<FrostReshareStep4> {
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final Map<String, int> resharers;
|
||||||
|
late final String myName;
|
||||||
|
late final int? myResharerIndexIndex;
|
||||||
|
late final String? myResharerComplete;
|
||||||
|
late final bool amOutgoingParticipant;
|
||||||
|
late final bool amNewParticipant;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
|
bool _buttonLock = false;
|
||||||
|
Future<void> _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<void>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<FrostReshareStep5> createState() => _FrostReshareStep5State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostReshareStep5State extends ConsumerState<FrostReshareStep5> {
|
||||||
|
late final String config;
|
||||||
|
late final String serializedKeys;
|
||||||
|
late final String reshareId;
|
||||||
|
|
||||||
|
late final bool isNew;
|
||||||
|
|
||||||
|
bool _buttonLock = false;
|
||||||
|
Future<void> _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<void>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import 'package:frostdart/frostdart.dart' as frost;
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/node_service_provider.dart';
|
import 'package:stackwallet/providers/global/node_service_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/prefs_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/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.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/logger.dart';
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
import 'package:stackwallet/utilities/show_loading.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
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/frost_wallet_info.dart';
|
||||||
import 'package:stackwallet/wallets/isar/models/wallet_info.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/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_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_mascot.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.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/qrcode_icon.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_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/stack_text_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
import 'package:stackwallet/pages/frost_mascot.dart';
|
|
||||||
|
|
||||||
class RestoreFrostMsWalletView extends ConsumerStatefulWidget {
|
class RestoreFrostMsWalletView extends ConsumerStatefulWidget {
|
||||||
const RestoreFrostMsWalletView({
|
const RestoreFrostMsWalletView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.walletName,
|
required this.walletName,
|
||||||
required this.coin,
|
required this.frostCurrency,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const String routeName = "/restoreFrostMsWalletView";
|
static const String routeName = "/restoreFrostMsWalletView";
|
||||||
|
|
||||||
final String walletName;
|
final String walletName;
|
||||||
final Coin coin;
|
final FrostCurrency frostCurrency;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<RestoreFrostMsWalletView> createState() =>
|
ConsumerState<RestoreFrostMsWalletView> createState() =>
|
||||||
|
@ -77,7 +75,7 @@ class _RestoreFrostMsWalletViewState
|
||||||
final myName = participants[myNameIndex];
|
final myName = participants[myNameIndex];
|
||||||
|
|
||||||
final info = WalletInfo.createNew(
|
final info = WalletInfo.createNew(
|
||||||
coin: widget.coin,
|
coin: widget.frostCurrency.coin,
|
||||||
name: widget.walletName,
|
name: widget.walletName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -132,7 +130,7 @@ class _RestoreFrostMsWalletViewState
|
||||||
whileFuture: _createWalletAndRecover(),
|
whileFuture: _createWalletAndRecover(),
|
||||||
context: context,
|
context: context,
|
||||||
message: "Restoring wallet...",
|
message: "Restoring wallet...",
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
ex = e;
|
ex = e;
|
||||||
},
|
},
|
||||||
|
@ -214,13 +212,15 @@ class _RestoreFrostMsWalletViewState
|
||||||
condition: Util.isDesktop,
|
condition: Util.isDesktop,
|
||||||
builder: (child) => DesktopScaffold(
|
builder: (child) => DesktopScaffold(
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
background: Theme.of(context).extension<StackColors>()!.background,
|
||||||
appBar: DesktopAppBar(
|
appBar: const DesktopAppBar(
|
||||||
isCompactHeight: false,
|
isCompactHeight: false,
|
||||||
leading: AppBarBackButton(),
|
leading: AppBarBackButton(),
|
||||||
|
// TODO: [prio=high] get rid of placeholder text??
|
||||||
trailing: FrostMascot(
|
trailing: FrostMascot(
|
||||||
title: 'Lorem ipsum',
|
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(
|
body: SizedBox(
|
||||||
width: 480,
|
width: 480,
|
||||||
|
|
|
@ -15,12 +15,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:isar/isar.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/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/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/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_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/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/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/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_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/name_generator.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
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/wallets/isar/models/wallet_info.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
@ -243,7 +244,7 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
||||||
height: isDesktop ? 0 : 16,
|
height: isDesktop ? 0 : 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Name your ${coin.prettyName} wallet",
|
"Name your ${coin.prettyName} ${coin.isFrost ? "multisig " : ""}wallet",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles.desktopH2(context)
|
? STextStyles.desktopH2(context)
|
||||||
|
@ -253,7 +254,7 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
||||||
height: isDesktop ? 16 : 8,
|
height: isDesktop ? 16 : 8,
|
||||||
),
|
),
|
||||||
Text(
|
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,
|
textAlign: TextAlign.center,
|
||||||
style: isDesktop
|
style: isDesktop
|
||||||
? STextStyles.desktopSubtitleH2(context)
|
? STextStyles.desktopSubtitleH2(context)
|
||||||
|
@ -394,7 +395,10 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
||||||
RestoreFrostMsWalletView.routeName,
|
RestoreFrostMsWalletView.routeName,
|
||||||
arguments: (
|
arguments: (
|
||||||
walletName: name,
|
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<NameYourWalletView> {
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Create config",
|
label: "Create new group",
|
||||||
enabled: _nextEnabled,
|
enabled: _nextEnabled,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final name = textEditingController.text;
|
final name = textEditingController.text;
|
||||||
|
@ -412,7 +416,10 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
||||||
CreateNewFrostMsWalletView.routeName,
|
CreateNewFrostMsWalletView.routeName,
|
||||||
arguments: (
|
arguments: (
|
||||||
walletName: name,
|
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<NameYourWalletView> {
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
SecondaryButton(
|
SecondaryButton(
|
||||||
label: "Import multisig config",
|
label: "Join group",
|
||||||
enabled: _nextEnabled,
|
enabled: _nextEnabled,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final name = textEditingController.text;
|
final name = textEditingController.text;
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
ImportNewFrostMsWalletView.routeName,
|
SelectNewFrostImportTypeView.routeName,
|
||||||
arguments: (
|
arguments: (
|
||||||
walletName: name,
|
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),
|
||||||
),
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
// 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)
|
if (!widget.coin.isFrost)
|
||||||
|
|
|
@ -79,7 +79,7 @@ class _FusionProgressViewState extends ConsumerState<FusionProgressView> {
|
||||||
Future<void>.delayed(const Duration(seconds: 2)),
|
Future<void>.delayed(const Duration(seconds: 2)),
|
||||||
]),
|
]),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
message: "Stopping fusion",
|
message: "Stopping fusion",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -349,7 +349,7 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
message: "Saving MonKey svg",
|
message: "Saving MonKey svg",
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
didError = true;
|
didError = true;
|
||||||
|
@ -402,7 +402,7 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
||||||
const Duration(seconds: 2)),
|
const Duration(seconds: 2)),
|
||||||
]),
|
]),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
message: "Downloading MonKey png",
|
message: "Downloading MonKey png",
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
didError = true;
|
didError = true;
|
||||||
|
@ -500,7 +500,7 @@ class _MonkeyViewState extends ConsumerState<MonkeyView> {
|
||||||
Future<void>.delayed(const Duration(seconds: 2)),
|
Future<void>.delayed(const Duration(seconds: 2)),
|
||||||
]),
|
]),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
message: "Fetching MonKey",
|
message: "Fetching MonKey",
|
||||||
subMessage: "We are fetching your MonKey",
|
subMessage: "We are fetching your MonKey",
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
|
|
|
@ -321,7 +321,7 @@ class _OrdinalImageGroup extends ConsumerWidget {
|
||||||
final filePath = await showLoading<String>(
|
final filePath = await showLoading<String>(
|
||||||
whileFuture: _savePngToFile(ref),
|
whileFuture: _savePngToFile(ref),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: true,
|
rootNavigator: true,
|
||||||
message: "Saving ordinal image",
|
message: "Saving ordinal image",
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
didError = true;
|
didError = true;
|
||||||
|
|
|
@ -1,404 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_continue_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostAttemptSignConfigView extends ConsumerStatefulWidget {
|
|
||||||
const FrostAttemptSignConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostAttemptSignConfigView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<FrostAttemptSignConfigView> createState() =>
|
|
||||||
_FrostAttemptSignConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostAttemptSignConfigViewState
|
|
||||||
extends ConsumerState<FrostAttemptSignConfigView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final String myName;
|
|
||||||
late final List<String> participantsWithoutMe;
|
|
||||||
late final String myPreprocess;
|
|
||||||
late final int myIndex;
|
|
||||||
late final int threshold;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
bool hasEnoughPreprocesses() {
|
|
||||||
// own preprocess is not included in controllers and must be set here
|
|
||||||
int count = 1;
|
|
||||||
|
|
||||||
for (final controller in controllers) {
|
|
||||||
if (controller.text.isNotEmpty) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count >= threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
final frostInfo = wallet.frostInfo;
|
|
||||||
|
|
||||||
myName = frostInfo.myName;
|
|
||||||
threshold = frostInfo.threshold;
|
|
||||||
participantsWithoutMe = List.from(frostInfo.participants); // Copy so it isn't fixed-length.
|
|
||||||
myIndex = participantsWithoutMe.indexOf(frostInfo.myName);
|
|
||||||
myPreprocess = ref.read(pFrostAttemptSignData.state).state!.preprocess;
|
|
||||||
|
|
||||||
participantsWithoutMe.removeAt(myIndex);
|
|
||||||
|
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Preprocesses",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: myPreprocess,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My name",
|
|
||||||
detail: myName,
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My preprocess",
|
|
||||||
detail: myPreprocess,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: myPreprocess,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: myPreprocess,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostPreprocessesTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter ${participantsWithoutMe[i]}'s preprocess",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Preprocess Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostPreprocessesClearButtonKey_$i",
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Preprocess Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostPreprocessesPasteButtonKey_$i",
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
|
||||||
key: Key(
|
|
||||||
"frostPreprocessesScanQrButtonKey_$i",
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Continue signing",
|
|
||||||
enabled: hasEnoughPreprocesses(),
|
|
||||||
onPressed: () async {
|
|
||||||
// collect Preprocess strings (not including my own)
|
|
||||||
final preprocesses = controllers.map((e) => e.text).toList();
|
|
||||||
|
|
||||||
// collect participants who are involved in this transaction
|
|
||||||
final List<String> requiredParticipantsUnordered = [];
|
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
|
||||||
if (preprocesses[i].isNotEmpty) {
|
|
||||||
requiredParticipantsUnordered.add(participantsWithoutMe[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref.read(pFrostSelectParticipantsUnordered.notifier).state =
|
|
||||||
requiredParticipantsUnordered;
|
|
||||||
|
|
||||||
// insert an empty string at my index
|
|
||||||
preprocesses.insert(myIndex, "");
|
|
||||||
|
|
||||||
try {
|
|
||||||
ref.read(pFrostContinueSignData.notifier).state =
|
|
||||||
Frost.continueSigning(
|
|
||||||
machinePtr:
|
|
||||||
ref.read(pFrostAttemptSignData.state).state!.machinePtr,
|
|
||||||
preprocesses: preprocesses,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
FrostContinueSignView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Failed to continue signing",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
|
|
||||||
class FrostCompleteSignView extends ConsumerStatefulWidget {
|
|
||||||
const FrostCompleteSignView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostCompleteSignView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<FrostCompleteSignView> createState() =>
|
|
||||||
_FrostCompleteSignViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostCompleteSignViewState extends ConsumerState<FrostCompleteSignView> {
|
|
||||||
bool _broadcastLock = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Preview transaction",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "Raw transaction hex",
|
|
||||||
detail: ref.watch(pFrostTxData.state).state!.raw!,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Broadcast Transaction",
|
|
||||||
onPressed: () async {
|
|
||||||
if (_broadcastLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_broadcastLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Exception? ex;
|
|
||||||
final txData = await showLoading(
|
|
||||||
whileFuture: ref
|
|
||||||
.read(pWallets)
|
|
||||||
.getWallet(widget.walletId)
|
|
||||||
.confirmSend(
|
|
||||||
txData: ref.read(pFrostTxData.state).state!,
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
message: "Broadcasting transaction to network",
|
|
||||||
isDesktop: Util.isDesktop,
|
|
||||||
onException: (e) {
|
|
||||||
ex = e;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ex != null) {
|
|
||||||
throw ex!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
if (txData != null) {
|
|
||||||
ref.read(pFrostTxData.state).state = txData;
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(
|
|
||||||
Util.isDesktop
|
|
||||||
? MyStackView.routeName
|
|
||||||
: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Broadcast error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_broadcastLock = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<FrostContinueSignView> createState() =>
|
|
||||||
_FrostContinueSignViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostContinueSignViewState extends ConsumerState<FrostContinueSignView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final String myName;
|
|
||||||
late final List<String> participantsWithoutMe;
|
|
||||||
late final List<String> participantsAll;
|
|
||||||
late final String myShare;
|
|
||||||
late final int myIndex;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
|
|
||||||
final frostInfo = wallet.frostInfo;
|
|
||||||
|
|
||||||
myName = frostInfo.myName;
|
|
||||||
participantsAll = frostInfo.participants;
|
|
||||||
myIndex = frostInfo.participants.indexOf(frostInfo.myName);
|
|
||||||
myShare = ref.read(pFrostContinueSignData.state).state!.share;
|
|
||||||
|
|
||||||
participantsWithoutMe = frostInfo.participants
|
|
||||||
.toSet()
|
|
||||||
.intersection(
|
|
||||||
ref.read(pFrostSelectParticipantsUnordered.state).state!.toSet())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
participantsWithoutMe.remove(myName);
|
|
||||||
|
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.transactionCreation,
|
|
||||||
popUntilOnYesRouteName: Util.isDesktop
|
|
||||||
? DesktopWalletView.routeName
|
|
||||||
: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.transactionCreation,
|
|
||||||
popUntilOnYesRouteName: DesktopWalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.transactionCreation,
|
|
||||||
popUntilOnYesRouteName: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Shares",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: myShare,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My name",
|
|
||||||
detail: myName,
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My shares",
|
|
||||||
detail: myShare,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: myShare,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: myShare,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostSharesTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter ${participantsWithoutMe[i]}'s share",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears "
|
|
||||||
"The Share Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostSharesClearButtonKey_$i",
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From "
|
|
||||||
"Clipboard To Share Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostSharesPasteButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Scan QR Button. Opens Camera "
|
|
||||||
"For Scanning QR Code.",
|
|
||||||
key: Key(
|
|
||||||
"frostSharesScanQrButtonKey_$i",
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions "
|
|
||||||
"while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Complete signing",
|
|
||||||
onPressed: () async {
|
|
||||||
// check for empty shares
|
|
||||||
if (controllers
|
|
||||||
.map((e) => e.text.isEmpty)
|
|
||||||
.reduce((value, element) => value |= element)) {
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Missing Shares",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect Share strings
|
|
||||||
final sharesCollected =
|
|
||||||
controllers.map((e) => e.text).toList();
|
|
||||||
|
|
||||||
final List<String> shares = [];
|
|
||||||
for (final participant in participantsAll) {
|
|
||||||
if (participantsWithoutMe.contains(participant)) {
|
|
||||||
shares.add(sharesCollected[
|
|
||||||
participantsWithoutMe.indexOf(participant)]);
|
|
||||||
} else {
|
|
||||||
shares.add("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final rawTx = Frost.completeSigning(
|
|
||||||
machinePtr: ref
|
|
||||||
.read(pFrostContinueSignData.state)
|
|
||||||
.state!
|
|
||||||
.machinePtr,
|
|
||||||
shares: shares,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostTxData.state).state =
|
|
||||||
ref.read(pFrostTxData.state).state!.copyWith(
|
|
||||||
raw: rawTx,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
FrostCompleteSignView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Failed to complete signing process",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
|
|
||||||
class FrostCreateSignConfigView extends ConsumerStatefulWidget {
|
|
||||||
const FrostCreateSignConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostCreateSignConfigView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<FrostCreateSignConfigView> createState() =>
|
|
||||||
_FrostCreateSignConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostCreateSignConfigViewState
|
|
||||||
extends ConsumerState<FrostCreateSignConfigView> {
|
|
||||||
bool _attemptSignLock = false;
|
|
||||||
|
|
||||||
Future<void> _attemptSign() async {
|
|
||||||
if (_attemptSignLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attemptSignLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
|
|
||||||
final attemptSignRes = await wallet.frostAttemptSignConfig(
|
|
||||||
config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
FrostAttemptSignConfigView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_attemptSignLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
double qrImageSize =
|
|
||||||
Util.isDesktop ? 360 : MediaQuery.of(context).size.width - 32;
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 600, // Was 480, may look better but overflows the bottom.
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Sign config",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
SizedBox(
|
|
||||||
height: qrImageSize,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
size: qrImageSize,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop)
|
|
||||||
const SizedBox(
|
|
||||||
height: 32,
|
|
||||||
),
|
|
||||||
DetailItem(
|
|
||||||
title: "Encoded config",
|
|
||||||
detail: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: Util.isDesktop ? 20 : 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop)
|
|
||||||
const Spacer(
|
|
||||||
flex: 2,
|
|
||||||
),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Attempt sign",
|
|
||||||
onPressed: () {
|
|
||||||
_attemptSign();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,332 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostImportSignConfigView extends ConsumerStatefulWidget {
|
|
||||||
const FrostImportSignConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostImportSignConfigView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<FrostImportSignConfigView> createState() =>
|
|
||||||
_FrostImportSignConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FrostImportSignConfigViewState
|
|
||||||
extends ConsumerState<FrostImportSignConfigView> {
|
|
||||||
late final TextEditingController configFieldController;
|
|
||||||
late final FocusNode configFocusNode;
|
|
||||||
|
|
||||||
bool _configEmpty = true;
|
|
||||||
|
|
||||||
bool _attemptSignLock = false;
|
|
||||||
|
|
||||||
Future<void> _attemptSign() async {
|
|
||||||
if (_attemptSignLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attemptSignLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
final config = configFieldController.text;
|
|
||||||
final wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
|
|
||||||
final data = Frost.extractDataFromSignConfig(
|
|
||||||
signConfig: config,
|
|
||||||
coin: wallet.cryptoCurrency,
|
|
||||||
);
|
|
||||||
|
|
||||||
final utxos = await ref
|
|
||||||
.read(mainDBProvider)
|
|
||||||
.getUTXOs(wallet.walletId)
|
|
||||||
.filter()
|
|
||||||
.anyOf(
|
|
||||||
data.inputs,
|
|
||||||
(q, e) => q
|
|
||||||
.txidEqualTo(Format.uint8listToString(e.hash))
|
|
||||||
.and()
|
|
||||||
.valueEqualTo(e.value)
|
|
||||||
.and()
|
|
||||||
.voutEqualTo(e.vout))
|
|
||||||
.findAll();
|
|
||||||
|
|
||||||
// TODO add more data from 'data' and display to user ?
|
|
||||||
ref.read(pFrostTxData.notifier).state = TxData(
|
|
||||||
frostMSConfig: config,
|
|
||||||
recipients: data.recipients
|
|
||||||
.map((e) => (address: e.address, amount: e.amount, isChange: false))
|
|
||||||
.toList(),
|
|
||||||
utxos: utxos.toSet(),
|
|
||||||
);
|
|
||||||
|
|
||||||
final attemptSignRes = await wallet.frostAttemptSignConfig(
|
|
||||||
config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
FrostAttemptSignConfigView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Import and attempt sign config failed",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_attemptSignLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
configFieldController = TextEditingController();
|
|
||||||
configFocusNode = FocusNode();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
configFieldController.dispose();
|
|
||||||
configFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Import FROST sign config",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: const Key("frConfigTextFieldKey"),
|
|
||||||
controller: configFieldController,
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
focusNode: configFocusNode,
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter config",
|
|
||||||
configFocusNode,
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: _configEmpty
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!_configEmpty
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Config Field.",
|
|
||||||
key: const Key("frConfigClearButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
configFieldController.text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Config Field Input.",
|
|
||||||
key: const Key("frConfigPasteButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
configFieldController.text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: _configEmpty
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (_configEmpty)
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
|
||||||
key: const Key("frConfigScanQrButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult = await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
configFieldController.text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Start signing",
|
|
||||||
enabled: !_configEmpty,
|
|
||||||
onPressed: () {
|
|
||||||
_attemptSign();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,10 +14,9 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_create_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/recipient.dart';
|
import 'package:stackwallet/pages/send_view/frost_ms/recipient.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
|
@ -25,7 +24,6 @@ import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount_formatter.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/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/show_loading.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/conditional_parent.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
import 'package:stackwallet/widgets/fee_slider.dart';
|
import 'package:stackwallet/widgets/fee_slider.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
@ -49,10 +50,10 @@ import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class FrostSendView extends ConsumerStatefulWidget {
|
class FrostSendView extends ConsumerStatefulWidget {
|
||||||
const FrostSendView({
|
const FrostSendView({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.coin,
|
required this.coin,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
static const String routeName = "/frostSendView";
|
static const String routeName = "/frostSendView";
|
||||||
|
|
||||||
|
@ -115,19 +116,33 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
whileFuture: _loadingFuture(),
|
whileFuture: _loadingFuture(),
|
||||||
context: context,
|
context: context,
|
||||||
message: "Generating sign config",
|
message: "Generating sign config",
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
throw e;
|
throw e;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final wallet =
|
||||||
|
ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet;
|
||||||
|
|
||||||
if (mounted && txData != null) {
|
if (mounted && txData != null) {
|
||||||
ref.read(pFrostTxData.notifier).state = txData;
|
ref.read(pFrostTxData.notifier).state = txData;
|
||||||
|
|
||||||
|
ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
|
info: (
|
||||||
|
walletName: wallet.info.name,
|
||||||
|
frostCurrency: wallet.cryptoCurrency,
|
||||||
|
),
|
||||||
|
walletId: walletId,
|
||||||
|
stepRoutes: FrostRouteGenerator.sendFrostTxStepRoutes,
|
||||||
|
parentNav: Navigator.of(context),
|
||||||
|
frostInterruptionDialogType:
|
||||||
|
FrostInterruptionDialogType.transactionCreation,
|
||||||
|
);
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
FrostCreateSignConfigView.routeName,
|
FrostStepScaffold.routeName,
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -168,16 +183,24 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
|
|
||||||
int customFeeRate = 1;
|
int customFeeRate = 1;
|
||||||
|
|
||||||
void _validateRecipientFormStates() {
|
bool _buttonEnabled = false;
|
||||||
|
|
||||||
|
bool _validateRecipientFormStatesHelper() {
|
||||||
for (final i in recipientWidgetIndexes) {
|
for (final i in recipientWidgetIndexes) {
|
||||||
final state = ref.read(pRecipient(i).state).state;
|
final state = ref.read(pRecipient(i));
|
||||||
if (state?.amount == null || state?.address == null) {
|
if (state?.amount == null ||
|
||||||
ref.read(previewTxButtonStateProvider.notifier).state = false;
|
state?.address == null ||
|
||||||
return;
|
state!.address.isEmpty) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref.read(previewTxButtonStateProvider.notifier).state = true;
|
return true;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
void _validateRecipientFormStates() {
|
||||||
|
setState(() {
|
||||||
|
_buttonEnabled = _validateRecipientFormStatesHelper();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -223,7 +246,7 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
FocusScope.of(context).unfocus();
|
FocusScope.of(context).unfocus();
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -232,40 +255,6 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
"Send ${coin.ticker}",
|
"Send ${coin.ticker}",
|
||||||
style: STextStyles.navBarTitle(context),
|
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<StackColors>()!.background,
|
|
||||||
icon: SvgPicture.asset(
|
|
||||||
Assets.svg.circlePlus,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
FrostImportSignConfigView.routeName,
|
|
||||||
arguments: walletId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
body: LayoutBuilder(
|
body: LayoutBuilder(
|
||||||
builder: (builderContext, constraints) {
|
builder: (builderContext, constraints) {
|
||||||
|
@ -349,14 +338,7 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
)
|
)
|
||||||
: const Spacer(),
|
: const Spacer(),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {},
|
||||||
// cryptoAmountController.text = ref
|
|
||||||
// .read(pAmountFormatter(coin))
|
|
||||||
// .format(
|
|
||||||
// _cachedBalance!,
|
|
||||||
// withUnitName: false,
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -372,24 +354,6 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.right,
|
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<FrostSendView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 16,
|
height: recipientWidgetIndexes.length > 1 ? 8 : 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,
|
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -437,6 +379,7 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
"recipientKey_${recipientWidgetIndexes[i]}",
|
"recipientKey_${recipientWidgetIndexes[i]}",
|
||||||
),
|
),
|
||||||
index: recipientWidgetIndexes[i],
|
index: recipientWidgetIndexes[i],
|
||||||
|
displayNumber: i + 1,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
onChanged: () {
|
onChanged: () {
|
||||||
_validateRecipientFormStates();
|
_validateRecipientFormStates();
|
||||||
|
@ -444,13 +387,46 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
remove: i == 0 && recipientWidgetIndexes.length == 1
|
remove: i == 0 && recipientWidgetIndexes.length == 1
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
|
ref
|
||||||
|
.read(pRecipient(recipientWidgetIndexes[i])
|
||||||
|
.notifier)
|
||||||
|
.state = null;
|
||||||
recipientWidgetIndexes.removeAt(i);
|
recipientWidgetIndexes.removeAt(i);
|
||||||
setState(() {});
|
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)
|
if (showCoinControl)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
|
@ -481,12 +457,6 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// finally spendable = ref
|
|
||||||
// .read(walletsChangeNotifierProvider)
|
|
||||||
// .getManager(widget.walletId)
|
|
||||||
// .balance
|
|
||||||
// .spendable;
|
|
||||||
|
|
||||||
// TODO: [prio=high] make sure this coincontrol works correctly
|
// TODO: [prio=high] make sure this coincontrol works correctly
|
||||||
|
|
||||||
Amount? amount;
|
Amount? amount;
|
||||||
|
@ -571,6 +541,7 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
),
|
),
|
||||||
child: FeeSlider(
|
child: FeeSlider(
|
||||||
coin: coin,
|
coin: coin,
|
||||||
|
showWU: true,
|
||||||
onSatVByteChanged: (rate) {
|
onSatVByteChanged: (rate) {
|
||||||
customFeeRate = rate;
|
customFeeRate = rate;
|
||||||
},
|
},
|
||||||
|
@ -584,21 +555,10 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
TextButton(
|
PrimaryButton(
|
||||||
onPressed: ref.watch(previewTxButtonStateProvider.state).state
|
label: "Create multisig transaction",
|
||||||
? _createSignConfig
|
enabled: _buttonEnabled,
|
||||||
: null,
|
onPressed: _createSignConfig,
|
||||||
style: ref.watch(previewTxButtonStateProvider.state).state
|
|
||||||
? Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.getPrimaryEnabledButtonStyle(context)
|
|
||||||
: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.getPrimaryDisabledButtonStyle(context),
|
|
||||||
child: Text(
|
|
||||||
"Create config",
|
|
||||||
style: STextStyles.button(context),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
|
@ -609,5 +569,3 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final previewTxButtonStateProvider = StateProvider((_) => false);
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.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/qrcode_icon.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
|
@ -45,16 +45,22 @@ class Recipient extends ConsumerStatefulWidget {
|
||||||
const Recipient({
|
const Recipient({
|
||||||
super.key,
|
super.key,
|
||||||
required this.index,
|
required this.index,
|
||||||
|
required this.displayNumber,
|
||||||
required this.coin,
|
required this.coin,
|
||||||
this.remove,
|
this.remove,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
|
required this.addAnotherRecipientTapped,
|
||||||
|
required this.sendAllTapped,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int index;
|
final int index;
|
||||||
|
final int displayNumber;
|
||||||
final Coin coin;
|
final Coin coin;
|
||||||
|
|
||||||
final VoidCallback? remove;
|
final VoidCallback? remove;
|
||||||
final VoidCallback? onChanged;
|
final VoidCallback? onChanged;
|
||||||
|
final VoidCallback addAnotherRecipientTapped;
|
||||||
|
final String Function() sendAllTapped;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<Recipient> createState() => _RecipientState();
|
ConsumerState<Recipient> createState() => _RecipientState();
|
||||||
|
@ -65,7 +71,9 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
late final FocusNode addressFocusNode, amountFocusNode;
|
late final FocusNode addressFocusNode, amountFocusNode;
|
||||||
|
|
||||||
bool _addressIsEmpty = true;
|
bool _addressIsEmpty = true;
|
||||||
bool _cryptoAmountChangeLock = false;
|
final bool _cryptoAmountChangeLock = false;
|
||||||
|
|
||||||
|
bool get isSingle => widget.remove == null;
|
||||||
|
|
||||||
void _updateRecipientData() {
|
void _updateRecipientData() {
|
||||||
final address = addressController.text;
|
final address = addressController.text;
|
||||||
|
@ -116,6 +124,16 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
amountController = TextEditingController();
|
amountController = TextEditingController();
|
||||||
// baseController = TextEditingController();
|
// baseController = TextEditingController();
|
||||||
|
|
||||||
|
final amount = ref.read(pRecipient(widget.index))?.amount;
|
||||||
|
if (amount != null) {
|
||||||
|
amountController.text = ref
|
||||||
|
.read(pAmountFormatter(widget.coin))
|
||||||
|
.format(amount, withUnitName: false);
|
||||||
|
}
|
||||||
|
addressController.text = ref.read(pRecipient(widget.index))?.address ?? "";
|
||||||
|
|
||||||
|
_addressIsEmpty = addressController.text.isEmpty;
|
||||||
|
|
||||||
addressFocusNode = FocusNode();
|
addressFocusNode = FocusNode();
|
||||||
amountFocusNode = FocusNode();
|
amountFocusNode = FocusNode();
|
||||||
// baseFocusNode = FocusNode();
|
// baseFocusNode = FocusNode();
|
||||||
|
@ -148,12 +166,31 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return RoundedWhiteContainer(
|
return RoundedContainer(
|
||||||
|
color: Colors.transparent,
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
isSingle ? "Send to" : "Recipient ${widget.displayNumber}",
|
||||||
|
style: STextStyles.smallMed12(context),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
text: isSingle ? "Add another recipient" : "Remove",
|
||||||
|
onTap:
|
||||||
|
isSingle ? widget.addAnotherRecipientTapped : widget.remove,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
Constants.size.circularBorderRadius,
|
Constants.size.circularBorderRadius,
|
||||||
|
@ -167,6 +204,7 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
focusNode: addressFocusNode,
|
focusNode: addressFocusNode,
|
||||||
style: STextStyles.field(context),
|
style: STextStyles.field(context),
|
||||||
onChanged: (_) {
|
onChanged: (_) {
|
||||||
|
_updateRecipientData();
|
||||||
setState(() {
|
setState(() {
|
||||||
_addressIsEmpty = addressController.text.isEmpty;
|
_addressIsEmpty = addressController.text.isEmpty;
|
||||||
});
|
});
|
||||||
|
@ -323,9 +361,33 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: 12,
|
height: isSingle ? 12 : 8,
|
||||||
),
|
),
|
||||||
|
if (isSingle)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Amount",
|
||||||
|
style: STextStyles.smallMed12(context),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
// disable send all since the frost tx creation logic isn't there (yet?)
|
||||||
|
const Spacer(),
|
||||||
|
// CustomTextButton(
|
||||||
|
// text: "Send all ${widget.coin.ticker}",
|
||||||
|
// onTap: () {
|
||||||
|
// amountController.text = widget.sendAllTapped();
|
||||||
|
// _cryptoAmountChanged();
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isSingle)
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
TextField(
|
TextField(
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
enableSuggestions: false,
|
enableSuggestions: false,
|
||||||
|
@ -335,6 +397,9 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
key: const Key("amountInputFieldCryptoTextFieldKey"),
|
key: const Key("amountInputFieldCryptoTextFieldKey"),
|
||||||
controller: amountController,
|
controller: amountController,
|
||||||
focusNode: amountFocusNode,
|
focusNode: amountFocusNode,
|
||||||
|
onChanged: (_) {
|
||||||
|
_updateRecipientData();
|
||||||
|
},
|
||||||
keyboardType: Util.isDesktop
|
keyboardType: Util.isDesktop
|
||||||
? null
|
? null
|
||||||
: const TextInputType.numberWithOptions(
|
: const TextInputType.numberWithOptions(
|
||||||
|
@ -375,126 +440,6 @@ class _RecipientState extends ConsumerState<Recipient> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// if (ref.watch(prefsChangeNotifierProvider
|
|
||||||
// .select((value) => value.externalCalls)))
|
|
||||||
// const SizedBox(
|
|
||||||
// height: 8,
|
|
||||||
// ),
|
|
||||||
// if (ref.watch(prefsChangeNotifierProvider
|
|
||||||
// .select((value) => value.externalCalls)))
|
|
||||||
// TextField(
|
|
||||||
// autocorrect: Util.isDesktop ? false : true,
|
|
||||||
// enableSuggestions: Util.isDesktop ? false : true,
|
|
||||||
// style: STextStyles.smallMed14(context).copyWith(
|
|
||||||
// color: Theme.of(context).extension<StackColors>()!.textDark,
|
|
||||||
// ),
|
|
||||||
// key: const Key("amountInputFieldFiatTextFieldKey"),
|
|
||||||
// controller: baseController,
|
|
||||||
// focusNode: baseFocusNode,
|
|
||||||
// keyboardType: Util.isDesktop
|
|
||||||
// ? null
|
|
||||||
// : const TextInputType.numberWithOptions(
|
|
||||||
// signed: false,
|
|
||||||
// decimal: true,
|
|
||||||
// ),
|
|
||||||
// textAlign: TextAlign.right,
|
|
||||||
// inputFormatters: [
|
|
||||||
// AmountInputFormatter(
|
|
||||||
// decimals: 2,
|
|
||||||
// locale: locale,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// onChanged: (baseAmountString) {
|
|
||||||
// final baseAmount = Amount.tryParseFiatString(
|
|
||||||
// baseAmountString,
|
|
||||||
// locale: locale,
|
|
||||||
// );
|
|
||||||
// Amount? cryptoAmount;
|
|
||||||
// final int decimals = widget.coin.decimals;
|
|
||||||
// if (baseAmount != null) {
|
|
||||||
// final _price = ref.read(_pPrice(widget.coin));
|
|
||||||
//
|
|
||||||
// if (_price == Decimal.zero) {
|
|
||||||
// cryptoAmount = 0.toAmountAsRaw(
|
|
||||||
// fractionDigits: decimals,
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// cryptoAmount = baseAmount <= Amount.zero
|
|
||||||
// ? 0.toAmountAsRaw(fractionDigits: decimals)
|
|
||||||
// : (baseAmount.decimal / _price)
|
|
||||||
// .toDecimal(
|
|
||||||
// scaleOnInfinitePrecision: decimals,
|
|
||||||
// )
|
|
||||||
// .toAmount(fractionDigits: decimals);
|
|
||||||
// }
|
|
||||||
// if (ref.read(pRecipient(widget.index))?.amount != null &&
|
|
||||||
// ref.read(pRecipient(widget.index))?.amount ==
|
|
||||||
// cryptoAmount) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final amountString =
|
|
||||||
// ref.read(pAmountFormatter(widget.coin)).format(
|
|
||||||
// cryptoAmount,
|
|
||||||
// withUnitName: false,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// _cryptoAmountChangeLock = true;
|
|
||||||
// amountController.text = amountString;
|
|
||||||
// _cryptoAmountChangeLock = false;
|
|
||||||
// } else {
|
|
||||||
// cryptoAmount = 0.toAmountAsRaw(
|
|
||||||
// fractionDigits: decimals,
|
|
||||||
// );
|
|
||||||
// _cryptoAmountChangeLock = true;
|
|
||||||
// amountController.text = "";
|
|
||||||
// _cryptoAmountChangeLock = false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// _updateRecipientData();
|
|
||||||
// },
|
|
||||||
// decoration: InputDecoration(
|
|
||||||
// contentPadding: const EdgeInsets.only(
|
|
||||||
// top: 12,
|
|
||||||
// right: 12,
|
|
||||||
// ),
|
|
||||||
// hintText: "0",
|
|
||||||
// hintStyle: STextStyles.fieldLabel(context).copyWith(
|
|
||||||
// fontSize: 14,
|
|
||||||
// ),
|
|
||||||
// prefixIcon: FittedBox(
|
|
||||||
// fit: BoxFit.scaleDown,
|
|
||||||
// child: Padding(
|
|
||||||
// padding: const EdgeInsets.all(12),
|
|
||||||
// child: Text(
|
|
||||||
// ref.watch(prefsChangeNotifierProvider
|
|
||||||
// .select((value) => value.currency)),
|
|
||||||
// style: STextStyles.smallMed14(context).copyWith(
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .accentColorDark),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
if (widget.remove != null)
|
|
||||||
const SizedBox(
|
|
||||||
height: 6,
|
|
||||||
),
|
|
||||||
if (widget.remove != null)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
CustomTextButton(
|
|
||||||
text: "Remove",
|
|
||||||
onTap: () {
|
|
||||||
ref.read(pRecipient(widget.index).notifier).state = null;
|
|
||||||
widget.remove?.call();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
236
lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart
Normal file
236
lib/pages/send_view/frost_ms/send_steps/frost_send_step_1a.dart
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||||
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/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/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class FrostSendStep1a extends ConsumerStatefulWidget {
|
||||||
|
const FrostSendStep1a({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/FrostSendStep1a";
|
||||||
|
static const String title = "FROST transaction";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostSendStep1a> createState() => _FrostSendStep1aState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
|
static const steps2to4 = [
|
||||||
|
"Wait for them to import the transaction config.",
|
||||||
|
"Verify that everyone has filled out their forms before continuing. If you "
|
||||||
|
"try to continue before everyone is ready, the process will be "
|
||||||
|
"canceled.",
|
||||||
|
"Check the box and press “Attempt sign”.",
|
||||||
|
];
|
||||||
|
|
||||||
|
late final int _threshold;
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
bool _attemptSignLock = false;
|
||||||
|
|
||||||
|
Future<void> _attemptSign() async {
|
||||||
|
if (_attemptSignLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_attemptSignLock = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
final attemptSignRes = await wallet.frostAttemptSignConfig(
|
||||||
|
config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
|
||||||
|
|
||||||
|
ref.read(pFrostCreateCurrentStep.state).state = 2;
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
ref
|
||||||
|
.read(pFrostScaffoldArgs)!
|
||||||
|
.stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
|
||||||
|
.routeName,
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"$e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
_attemptSignLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
_threshold = wallet.frostInfo.threshold;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(pFrostMyName.state).state = wallet.frostInfo.myName;
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final double qrImageSize =
|
||||||
|
Util.isDesktop ? 360 : MediaQuery.of(context).size.width / 1.67;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"1.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
"Share this config with the group members. ",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
"You must have the threshold number of signatures (including yours) to send the transaction.",
|
||||||
|
style: STextStyles.w600_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
for (int i = 0; i < steps2to4.length; i++)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${i + 2}.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
steps2to4[i],
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Util.isDesktop ? 20 : 12,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: qrImageSize,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
QrImageView(
|
||||||
|
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
|
||||||
|
size: qrImageSize,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
foregroundColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorDark,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Util.isDesktop ? 20 : 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Encoded transaction 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 : 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Threshold",
|
||||||
|
detail: "$_threshold signatures",
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Util.isDesktop ? 20 : 12,
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop)
|
||||||
|
const Spacer(
|
||||||
|
flex: 2,
|
||||||
|
),
|
||||||
|
CheckboxTextButton(
|
||||||
|
label: "I have verified that everyone has imported the config and "
|
||||||
|
"is ready to sign",
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: Util.isDesktop ? 20 : 12,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Attempt sign",
|
||||||
|
enabled: _userVerifyContinue,
|
||||||
|
onPressed: () {
|
||||||
|
_attemptSign();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
196
lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart
Normal file
196
lib/pages/send_view/frost_ms/send_steps/frost_send_step_1b.dart
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/services/frost.dart';
|
||||||
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||||
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
|
import 'package:stackwallet/widgets/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 FrostSendStep1b extends ConsumerStatefulWidget {
|
||||||
|
const FrostSendStep1b({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/FrostSendStep1b";
|
||||||
|
static const String title = "Sign FROST transaction";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostSendStep1b> createState() => _FrostSendStep1bState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
||||||
|
static const info = [
|
||||||
|
"Scan the config QR code or paste the code provided by the member "
|
||||||
|
"initiating this transaction.",
|
||||||
|
"Wait for other members to finish entering their information.",
|
||||||
|
"Verify that everyone has filled out their forms before continuing. If you "
|
||||||
|
"try to continue before everyone is ready, the process will be "
|
||||||
|
"canceled.",
|
||||||
|
"Check the box and press “Start signing”.",
|
||||||
|
];
|
||||||
|
|
||||||
|
late final TextEditingController configFieldController;
|
||||||
|
late final FocusNode configFocusNode;
|
||||||
|
|
||||||
|
bool _configEmpty = true, _userVerifyContinue = false;
|
||||||
|
|
||||||
|
bool _attemptSignLock = false;
|
||||||
|
|
||||||
|
Future<void> _attemptSign() async {
|
||||||
|
if (_attemptSignLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_attemptSignLock = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (FocusScope.of(context).hasFocus) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
final config = configFieldController.text;
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
final data = Frost.extractDataFromSignConfig(
|
||||||
|
signConfig: config,
|
||||||
|
coin: wallet.cryptoCurrency,
|
||||||
|
);
|
||||||
|
|
||||||
|
final utxos = await ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.getUTXOs(wallet.walletId)
|
||||||
|
.filter()
|
||||||
|
.anyOf(
|
||||||
|
data.inputs,
|
||||||
|
(q, e) => q
|
||||||
|
.txidEqualTo(Format.uint8listToString(e.hash))
|
||||||
|
.and()
|
||||||
|
.valueEqualTo(e.value)
|
||||||
|
.and()
|
||||||
|
.voutEqualTo(e.vout))
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
// TODO add more data from 'data' and display to user ?
|
||||||
|
ref.read(pFrostTxData.notifier).state = TxData(
|
||||||
|
frostMSConfig: config,
|
||||||
|
recipients: data.recipients
|
||||||
|
.map((e) => (address: e.address, amount: e.amount, isChange: false))
|
||||||
|
.toList(),
|
||||||
|
utxos: utxos.toSet(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final attemptSignRes = await wallet.frostAttemptSignConfig(
|
||||||
|
config: ref.read(pFrostTxData.state).state!.frostMSConfig!,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.read(pFrostAttemptSignData.notifier).state = attemptSignRes;
|
||||||
|
|
||||||
|
ref.read(pFrostCreateCurrentStep.state).state = 2;
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
ref
|
||||||
|
.read(pFrostScaffoldArgs)!
|
||||||
|
.stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
|
||||||
|
.routeName,
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"$e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Import and attempt sign config failed",
|
||||||
|
message: e.toString(),
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
_attemptSignLock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
configFieldController = TextEditingController();
|
||||||
|
configFocusNode = FocusNode();
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(pFrostMyName.state).state = wallet.frostInfo.myName;
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
configFieldController.dispose();
|
||||||
|
configFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const FrostStepUserSteps(
|
||||||
|
userSteps: info,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
FrostStepField(
|
||||||
|
controller: configFieldController,
|
||||||
|
focusNode: configFocusNode,
|
||||||
|
showQrScanOption: true,
|
||||||
|
label: "Import sign config",
|
||||||
|
hint: "Enter config",
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {
|
||||||
|
_configEmpty = configFieldController.text.isEmpty;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop) const Spacer(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
CheckboxTextButton(
|
||||||
|
label: "I have verified that everyone has imported he config and"
|
||||||
|
" is ready to sign",
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Start signing",
|
||||||
|
enabled: !_configEmpty && _userVerifyContinue,
|
||||||
|
onPressed: () {
|
||||||
|
_attemptSign();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
310
lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart
Normal file
310
lib/pages/send_view/frost_ms/send_steps/frost_send_step_2.dart
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
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/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/services/frost.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/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/rounded_white_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
|
||||||
|
|
||||||
|
class FrostSendStep2 extends ConsumerStatefulWidget {
|
||||||
|
const FrostSendStep2({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/FrostSendStep2";
|
||||||
|
static const String title = "Preprocesses";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostSendStep2> createState() => _FrostSendStep2State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final String myName;
|
||||||
|
late final List<String> participantsWithoutMe;
|
||||||
|
late final String myPreprocess;
|
||||||
|
late final int myIndex;
|
||||||
|
late final int threshold;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
int countPreprocesses() {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
final frostInfo = wallet.frostInfo;
|
||||||
|
|
||||||
|
myName = frostInfo.myName;
|
||||||
|
threshold = frostInfo.threshold;
|
||||||
|
participantsWithoutMe =
|
||||||
|
List.from(frostInfo.participants); // Copy so it isn't fixed-length.
|
||||||
|
myIndex = participantsWithoutMe.indexOf(frostInfo.myName);
|
||||||
|
myPreprocess = ref.read(pFrostAttemptSignData.state).state!.preprocess;
|
||||||
|
|
||||||
|
participantsWithoutMe.removeAt(myIndex);
|
||||||
|
|
||||||
|
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
||||||
|
controllers.add(TextEditingController());
|
||||||
|
focusNodes.add(FocusNode());
|
||||||
|
fieldIsEmptyFlags.add(true);
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (int i = 0; i < controllers.length; i++) {
|
||||||
|
controllers[i].dispose();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < focusNodes.length; i++) {
|
||||||
|
focusNodes[i].dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"1.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Share your preprocess with other signing group members.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"2.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
"Enter their preprocesses into the corresponding fields. ",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: "You must have the threshold number of "
|
||||||
|
"preprocesses (including yours) to send this transaction.",
|
||||||
|
style: STextStyles.w600_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Threshold",
|
||||||
|
detail: "$threshold signatures",
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "My name",
|
||||||
|
detail: myName,
|
||||||
|
button: Util.isDesktop
|
||||||
|
? IconCopyButton(
|
||||||
|
data: myName,
|
||||||
|
)
|
||||||
|
: SimpleCopyButton(
|
||||||
|
data: myName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "My preprocess",
|
||||||
|
detail: myPreprocess,
|
||||||
|
button: Util.isDesktop
|
||||||
|
? IconCopyButton(
|
||||||
|
data: myPreprocess,
|
||||||
|
)
|
||||||
|
: SimpleCopyButton(
|
||||||
|
data: myPreprocess,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
FrostQrDialogPopupButton(
|
||||||
|
data: myPreprocess,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Text(
|
||||||
|
"You need to obtain ${threshold - 1} preprocess from signing members to send this transaction.",
|
||||||
|
style: STextStyles.label(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Builder(builder: (context) {
|
||||||
|
final count = countPreprocesses();
|
||||||
|
final colors = Theme.of(context).extension<StackColors>()!;
|
||||||
|
return DetailItem(
|
||||||
|
title: "Required preprocesses",
|
||||||
|
detail: "$count of $threshold",
|
||||||
|
horizontal: true,
|
||||||
|
overrideDetailTextColor: count >= threshold
|
||||||
|
? colors.accentColorGreen
|
||||||
|
: colors.accentColorRed,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||||
|
FrostStepField(
|
||||||
|
label: participantsWithoutMe[i],
|
||||||
|
hint: "Enter ${participantsWithoutMe[i]}'s preprocess",
|
||||||
|
controller: controllers[i],
|
||||||
|
focusNode: focusNodes[i],
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {
|
||||||
|
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showQrScanOption: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop) const Spacer(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Generate shares",
|
||||||
|
enabled: countPreprocesses() >= threshold,
|
||||||
|
onPressed: () async {
|
||||||
|
// collect Preprocess strings (not including my own)
|
||||||
|
final preprocesses = controllers.map((e) => e.text).toList();
|
||||||
|
|
||||||
|
// collect participants who are involved in this transaction
|
||||||
|
final List<String> requiredParticipantsUnordered = [];
|
||||||
|
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
||||||
|
if (preprocesses[i].isNotEmpty) {
|
||||||
|
requiredParticipantsUnordered.add(participantsWithoutMe[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.read(pFrostSelectParticipantsUnordered.notifier).state =
|
||||||
|
requiredParticipantsUnordered;
|
||||||
|
|
||||||
|
// insert an empty string at my index
|
||||||
|
preprocesses.insert(myIndex, "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ref.read(pFrostContinueSignData.notifier).state =
|
||||||
|
Frost.continueSigning(
|
||||||
|
machinePtr:
|
||||||
|
ref.read(pFrostAttemptSignData.state).state!.machinePtr,
|
||||||
|
preprocesses: preprocesses,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.read(pFrostCreateCurrentStep.state).state = 3;
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
ref
|
||||||
|
.read(pFrostScaffoldArgs)!
|
||||||
|
.stepRoutes[ref.read(pFrostCreateCurrentStep) - 1]
|
||||||
|
.routeName,
|
||||||
|
);
|
||||||
|
|
||||||
|
// await Navigator.of(context).pushNamed(
|
||||||
|
// FrostContinueSignView.routeName,
|
||||||
|
// arguments: widget.walletId,
|
||||||
|
// );
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"$e\n$s",
|
||||||
|
level: LogLevel.Fatal,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Failed to continue signing",
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
254
lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart
Normal file
254
lib/pages/send_view/frost_ms/send_steps/frost_send_step_3.dart
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
import 'package:coinlib_flutter/coinlib_flutter.dart' as cl;
|
||||||
|
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/providers/global/wallets_provider.dart';
|
||||||
|
import 'package:stackwallet/services/frost.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.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/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 FrostSendStep3 extends ConsumerStatefulWidget {
|
||||||
|
const FrostSendStep3({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/FrostSendStep3";
|
||||||
|
static const String title = "Shares";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostSendStep3> createState() => _FrostSendStep3State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
|
static const info = [
|
||||||
|
"Send your share to other signing group members.",
|
||||||
|
"Enter their shares into the corresponding fields.",
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
late final String myName;
|
||||||
|
late final List<String> participantsWithoutMe;
|
||||||
|
late final List<String> participantsAll;
|
||||||
|
late final String myShare;
|
||||||
|
late final int myIndex;
|
||||||
|
|
||||||
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
final frostInfo = wallet.frostInfo;
|
||||||
|
|
||||||
|
myName = frostInfo.myName;
|
||||||
|
participantsAll = frostInfo.participants;
|
||||||
|
myIndex = frostInfo.participants.indexOf(frostInfo.myName);
|
||||||
|
myShare = ref.read(pFrostContinueSignData.state).state!.share;
|
||||||
|
|
||||||
|
participantsWithoutMe = frostInfo.participants
|
||||||
|
.toSet()
|
||||||
|
.intersection(
|
||||||
|
ref.read(pFrostSelectParticipantsUnordered.state).state!.toSet())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
participantsWithoutMe.remove(myName);
|
||||||
|
|
||||||
|
for (int i = 0; i < participantsWithoutMe.length; i++) {
|
||||||
|
controllers.add(TextEditingController());
|
||||||
|
focusNodes.add(FocusNode());
|
||||||
|
fieldIsEmptyFlags.add(true);
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (int i = 0; i < controllers.length; i++) {
|
||||||
|
controllers[i].dispose();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < focusNodes.length; i++) {
|
||||||
|
focusNodes[i].dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const FrostStepUserSteps(
|
||||||
|
userSteps: info,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "My name",
|
||||||
|
detail: myName,
|
||||||
|
button: Util.isDesktop
|
||||||
|
? IconCopyButton(
|
||||||
|
data: myName,
|
||||||
|
)
|
||||||
|
: SimpleCopyButton(
|
||||||
|
data: myName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||||
|
FrostStepField(
|
||||||
|
label: participantsWithoutMe[i],
|
||||||
|
hint: "Enter ${participantsWithoutMe[i]}'s share",
|
||||||
|
controller: controllers[i],
|
||||||
|
focusNode: focusNodes[i],
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() {
|
||||||
|
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showQrScanOption: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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: 12,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Generate transaction",
|
||||||
|
enabled: _userVerifyContinue &&
|
||||||
|
!fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
||||||
|
onPressed: () async {
|
||||||
|
// collect Share strings
|
||||||
|
final sharesCollected = controllers.map((e) => e.text).toList();
|
||||||
|
|
||||||
|
final List<String> shares = [];
|
||||||
|
for (final participant in participantsAll) {
|
||||||
|
if (participantsWithoutMe.contains(participant)) {
|
||||||
|
shares.add(sharesCollected[
|
||||||
|
participantsWithoutMe.indexOf(participant)]);
|
||||||
|
} else {
|
||||||
|
shares.add("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final rawTx = Frost.completeSigning(
|
||||||
|
machinePtr:
|
||||||
|
ref.read(pFrostContinueSignData.state).state!.machinePtr,
|
||||||
|
shares: shares,
|
||||||
|
);
|
||||||
|
|
||||||
|
final tx = cl.Transaction.fromHex(rawTx);
|
||||||
|
final txData = ref.read(pFrostTxData)!;
|
||||||
|
|
||||||
|
final fractionDigits =
|
||||||
|
txData.recipients!.first.amount.fractionDigits;
|
||||||
|
|
||||||
|
final inputTotal = Amount(
|
||||||
|
rawValue: txData.utxos!
|
||||||
|
.map((e) => BigInt.from(e.value))
|
||||||
|
.reduce((v, e) => v += e),
|
||||||
|
fractionDigits: fractionDigits,
|
||||||
|
);
|
||||||
|
final outputTotal = Amount(
|
||||||
|
rawValue:
|
||||||
|
tx.outputs.map((e) => e.value).reduce((v, e) => v += e),
|
||||||
|
fractionDigits: fractionDigits,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.read(pFrostTxData.state).state = txData.copyWith(
|
||||||
|
raw: rawTx,
|
||||||
|
fee: inputTotal - outputTotal,
|
||||||
|
frostSigners: [
|
||||||
|
myName,
|
||||||
|
...participantsWithoutMe,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
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<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Failed to complete signing process",
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
296
lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart
Normal file
296
lib/pages/send_view/frost_ms/send_steps/frost_send_step_4.dart
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
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/wallet_view/transaction_views/tx_v2/transaction_v2_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/amount/amount_formatter.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/show_loading.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.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/expandable.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
|
class FrostSendStep4 extends ConsumerStatefulWidget {
|
||||||
|
const FrostSendStep4({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/FrostSendStep4";
|
||||||
|
static const String title = "Preview transaction";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostSendStep4> createState() => _FrostSendStep4State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
|
final List<bool> _expandedStates = [];
|
||||||
|
|
||||||
|
bool _broadcastLock = false;
|
||||||
|
|
||||||
|
late final CryptoCurrency cryptoCurrency;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
cryptoCurrency = wallet.cryptoCurrency;
|
||||||
|
|
||||||
|
for (final _ in ref.read(pFrostTxData)!.recipients!) {
|
||||||
|
_expandedStates.add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final signerNames = ref.watch(pFrostTxData)!.frostSigners!;
|
||||||
|
final recipients = ref.watch(pFrostTxData)!.recipients!;
|
||||||
|
|
||||||
|
final String signers;
|
||||||
|
if (signerNames.length > 1) {
|
||||||
|
signers = signerNames
|
||||||
|
.sublist(1)
|
||||||
|
.fold(signerNames.first, (pv, e) => pv += ", $e");
|
||||||
|
} else {
|
||||||
|
signers = signerNames.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (kDebugMode)
|
||||||
|
DetailItem(
|
||||||
|
title: "Tx hex (debug mode only)",
|
||||||
|
detail: ref.watch(pFrostTxData)!.raw!,
|
||||||
|
button: Util.isDesktop
|
||||||
|
? IconCopyButton(
|
||||||
|
data: ref.watch(pFrostTxData)!.raw!,
|
||||||
|
)
|
||||||
|
: SimpleCopyButton(
|
||||||
|
data: ref.watch(pFrostTxData)!.raw!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Send ${cryptoCurrency.coin.ticker}",
|
||||||
|
style: STextStyles.w600_20(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
recipients.length == 1
|
||||||
|
? _Recipient(
|
||||||
|
address: recipients[0].address,
|
||||||
|
amount: ref
|
||||||
|
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||||
|
.format(recipients[0].amount),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < recipients.length; i++)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10),
|
||||||
|
child: Expandable(
|
||||||
|
onExpandChanged: (state) {
|
||||||
|
setState(() {
|
||||||
|
_expandedStates[i] =
|
||||||
|
state == ExpandableState.expanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
header: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12, bottom: 6),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Recipient ${i + 1}",
|
||||||
|
style: STextStyles.itemSubtitle(context),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
_expandedStates[i]
|
||||||
|
? Assets.svg.chevronUp
|
||||||
|
: Assets.svg.chevronDown,
|
||||||
|
width: 12,
|
||||||
|
height: 6,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: _Recipient(
|
||||||
|
address: recipients[i].address,
|
||||||
|
amount: ref
|
||||||
|
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||||
|
.format(recipients[i].amount),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Transaction fee",
|
||||||
|
detail: ref
|
||||||
|
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||||
|
.format(ref.watch(pFrostTxData)!.fee!),
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Total",
|
||||||
|
detail: ref.watch(pAmountFormatter(cryptoCurrency.coin)).format(
|
||||||
|
ref.watch(pFrostTxData)!.fee! +
|
||||||
|
recipients.map((e) => e.amount).reduce((v, e) => v += e)),
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Note",
|
||||||
|
detail: ref.watch(pFrostTxData)!.note ?? "",
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Signers",
|
||||||
|
detail: signers,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop) const Spacer(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Approve transaction",
|
||||||
|
onPressed: () async {
|
||||||
|
if (_broadcastLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_broadcastLock = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Exception? ex;
|
||||||
|
final txData = await showLoading(
|
||||||
|
whileFuture: ref
|
||||||
|
.read(pWallets)
|
||||||
|
.getWallet(
|
||||||
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
|
)
|
||||||
|
.confirmSend(
|
||||||
|
txData: ref.read(pFrostTxData)!,
|
||||||
|
),
|
||||||
|
context: context,
|
||||||
|
message: "Broadcasting transaction to network",
|
||||||
|
rootNavigator: true, // used to pop using root nav
|
||||||
|
onException: (e) {
|
||||||
|
ex = e;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ex != null) {
|
||||||
|
throw ex!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
if (txData != null) {
|
||||||
|
ref.read(pFrostScaffoldCanPopDesktop.notifier).state = true;
|
||||||
|
ref.read(pFrostTxData.state).state = txData;
|
||||||
|
ref.read(pFrostScaffoldArgs)!.parentNav.popUntil(
|
||||||
|
ModalRoute.withName(
|
||||||
|
Util.isDesktop
|
||||||
|
? MyStackView.routeName
|
||||||
|
: WalletView.routeName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"$e\n$s",
|
||||||
|
level: LogLevel.Fatal,
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
return await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: "Broadcast error",
|
||||||
|
message: e.toString(),
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
onOkPressed:
|
||||||
|
Navigator.of(context, rootNavigator: true).pop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_broadcastLock = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Recipient extends StatelessWidget {
|
||||||
|
const _Recipient({
|
||||||
|
super.key,
|
||||||
|
required this.address,
|
||||||
|
required this.amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String address;
|
||||||
|
final String amount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
DetailItem(
|
||||||
|
title: "Address",
|
||||||
|
detail: address,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Amount",
|
||||||
|
detail: amount,
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,17 +16,19 @@ import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
|
||||||
class SettingsListButton extends StatelessWidget {
|
class SettingsListButton extends StatelessWidget {
|
||||||
const SettingsListButton({
|
const SettingsListButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.iconAssetName,
|
required this.iconAssetName,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
this.iconSize = 20.0,
|
this.iconSize = 20.0,
|
||||||
}) : super(key: key);
|
this.padding = const EdgeInsets.all(8.0),
|
||||||
|
});
|
||||||
|
|
||||||
final String iconAssetName;
|
final String iconAssetName;
|
||||||
final String title;
|
final String title;
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -44,7 +46,7 @@ class SettingsListButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: padding,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|
|
@ -10,34 +10,40 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/begin_reshare_config_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
||||||
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
import 'package:stackwallet/widgets/conditional_parent.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/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class FrostMSWalletOptionsView extends ConsumerWidget {
|
class FrostMSWalletOptionsView extends ConsumerWidget {
|
||||||
const FrostMSWalletOptionsView({
|
const FrostMSWalletOptionsView({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
static const String routeName = "/frostMSWalletOptionsView";
|
static const String routeName = "/frostMSWalletOptionsView";
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
|
|
||||||
|
static const _padding = 12.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
|
@ -83,56 +89,86 @@ class FrostMSWalletOptionsView extends ConsumerWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_OptionButton(
|
RoundedWhiteContainer(
|
||||||
label: "Show participants",
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () {
|
child: SettingsListButton(
|
||||||
Navigator.of(context).pushNamed(
|
padding: const EdgeInsets.all(_padding),
|
||||||
FrostParticipantsView.routeName,
|
title: "Show participants",
|
||||||
arguments: walletId,
|
iconAssetName: Assets.svg.peers,
|
||||||
);
|
onPressed: () {
|
||||||
},
|
Navigator.of(context).pushNamed(
|
||||||
|
FrostParticipantsView.routeName,
|
||||||
|
arguments: walletId,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
_OptionButton(
|
RoundedWhiteContainer(
|
||||||
label: "Initiate resharing",
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () {
|
child: SettingsListButton(
|
||||||
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
padding: const EdgeInsets.all(_padding),
|
||||||
final frostInfo = ref
|
title: "Initiate resharing",
|
||||||
.read(mainDBProvider)
|
iconAssetName: Assets.svg.swap2,
|
||||||
.isar
|
onPressed: () {
|
||||||
.frostWalletInfo
|
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
||||||
.getByWalletIdSync(walletId)!;
|
final frostInfo = ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.frostWalletInfo
|
||||||
|
.getByWalletIdSync(walletId)!;
|
||||||
|
|
||||||
ref.read(pFrostMyName.state).state = frostInfo.myName;
|
ref.read(pFrostMyName.state).state = frostInfo.myName;
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
BeginReshareConfigView.routeName,
|
InitiateResharingView.routeName,
|
||||||
arguments: walletId,
|
arguments: walletId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
_OptionButton(
|
RoundedWhiteContainer(
|
||||||
label: "Import reshare config",
|
padding: EdgeInsets.zero,
|
||||||
onPressed: () {
|
child: SettingsListButton(
|
||||||
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
padding: const EdgeInsets.all(_padding),
|
||||||
final frostInfo = ref
|
title: "Import reshare config",
|
||||||
.read(mainDBProvider)
|
iconAssetName: Assets.svg.downloadFolder,
|
||||||
.isar
|
iconSize: 16,
|
||||||
.frostWalletInfo
|
onPressed: () {
|
||||||
.getByWalletIdSync(walletId)!;
|
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
||||||
|
final frostInfo = ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.frostWalletInfo
|
||||||
|
.getByWalletIdSync(walletId)!;
|
||||||
|
|
||||||
ref.read(pFrostMyName.state).state = frostInfo.myName;
|
ref.read(pFrostMyName.state).state = frostInfo.myName;
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(
|
final wallet = ref.read(pWallets).getWallet(walletId)
|
||||||
ImportReshareConfigView.routeName,
|
as BitcoinFrostWallet;
|
||||||
arguments: walletId,
|
|
||||||
);
|
ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
},
|
info: (
|
||||||
|
walletName: wallet.info.name,
|
||||||
|
frostCurrency: wallet.cryptoCurrency,
|
||||||
|
),
|
||||||
|
walletId: wallet.walletId,
|
||||||
|
stepRoutes: FrostRouteGenerator.importReshareStepRoutes,
|
||||||
|
parentNav: Navigator.of(context),
|
||||||
|
frostInterruptionDialogType:
|
||||||
|
FrostInterruptionDialogType.resharing,
|
||||||
|
);
|
||||||
|
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
FrostStepScaffold.routeName,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -142,45 +178,3 @@ class FrostMSWalletOptionsView extends ConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OptionButton extends StatelessWidget {
|
|
||||||
const _OptionButton({
|
|
||||||
super.key,
|
|
||||||
required this.label,
|
|
||||||
required this.onPressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return RoundedWhiteContainer(
|
|
||||||
padding: const EdgeInsets.all(0),
|
|
||||||
child: RawMaterialButton(
|
|
||||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
onPressed: onPressed,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12.0,
|
|
||||||
vertical: 20,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: STextStyles.titleBold12(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.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/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
||||||
|
@ -11,6 +14,7 @@ import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class FrostParticipantsView extends ConsumerWidget {
|
class FrostParticipantsView extends ConsumerWidget {
|
||||||
const FrostParticipantsView({
|
const FrostParticipantsView({
|
||||||
|
@ -84,31 +88,56 @@ class FrostParticipantsView extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: Util.isDesktop
|
||||||
|
? CrossAxisAlignment.start
|
||||||
|
: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < frostInfo.participants.length; i++)
|
for (int i = 0; i < frostInfo.participants.length; i++)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 5,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: RoundedWhiteContainer(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Container(
|
||||||
Text(
|
width: 26,
|
||||||
"Index $i",
|
height: 26,
|
||||||
style: STextStyles.label(context),
|
decoration: BoxDecoration(
|
||||||
),
|
color: Theme.of(context)
|
||||||
const SizedBox(
|
.extension<StackColors>()!
|
||||||
height: 6,
|
.textFieldActiveBG,
|
||||||
),
|
borderRadius: BorderRadius.circular(
|
||||||
SelectableText(
|
200,
|
||||||
frostInfo.participants[i] == frostInfo.myName
|
),
|
||||||
? "${frostInfo.participants[i]} (me)"
|
),
|
||||||
: frostInfo.participants[i],
|
child: Center(
|
||||||
style: STextStyles.itemSubtitle12(context),
|
child: SvgPicture.asset(
|
||||||
),
|
Assets.svg.user,
|
||||||
],
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
frostInfo.participants[i] == frostInfo.myName
|
||||||
|
? "${frostInfo.participants[i]} (me)"
|
||||||
|
: frostInfo.participants[i],
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
IconCopyButton(
|
||||||
|
data: frostInfo.participants[i],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:frostdart/frostdart.dart';
|
import 'package:frostdart/frostdart.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart';
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/services/frost.dart';
|
import 'package:stackwallet/services/frost.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
import 'package:stackwallet/utilities/format.dart';
|
||||||
|
@ -13,12 +16,15 @@ import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
||||||
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
import 'package:stackwallet/widgets/conditional_parent.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/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
final class CompleteReshareConfigView extends ConsumerStatefulWidget {
|
final class CompleteReshareConfigView extends ConsumerStatefulWidget {
|
||||||
|
@ -31,7 +37,7 @@ final class CompleteReshareConfigView extends ConsumerStatefulWidget {
|
||||||
static const String routeName = "/completeReshareConfigView";
|
static const String routeName = "/completeReshareConfigView";
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final List<int> resharers;
|
final Map<String, int> resharers;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<CompleteReshareConfigView> createState() =>
|
ConsumerState<CompleteReshareConfigView> createState() =>
|
||||||
|
@ -45,9 +51,12 @@ class _CompleteReshareConfigViewState
|
||||||
|
|
||||||
final List<TextEditingController> controllers = [];
|
final List<TextEditingController> controllers = [];
|
||||||
|
|
||||||
|
late final String myName;
|
||||||
|
|
||||||
int _participantsCount = 0;
|
int _participantsCount = 0;
|
||||||
|
|
||||||
bool _buttonLock = false;
|
bool _buttonLock = false;
|
||||||
|
bool _includeMeInReshare = false;
|
||||||
|
|
||||||
Future<void> _onPressed() async {
|
Future<void> _onPressed() async {
|
||||||
if (_buttonLock) {
|
if (_buttonLock) {
|
||||||
|
@ -74,10 +83,16 @@ class _CompleteReshareConfigViewState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<String> newParticipants =
|
||||||
|
controllers.map((e) => e.text.trim()).toList();
|
||||||
|
if (_includeMeInReshare) {
|
||||||
|
newParticipants.insert(0, myName);
|
||||||
|
}
|
||||||
|
|
||||||
final config = Frost.createResharerConfig(
|
final config = Frost.createResharerConfig(
|
||||||
newThreshold: int.parse(_newThresholdController.text),
|
newThreshold: int.parse(_newThresholdController.text),
|
||||||
resharers: widget.resharers,
|
resharers: widget.resharers.values.toList(),
|
||||||
newParticipants: controllers.map((e) => e.text).toList(),
|
newParticipants: newParticipants,
|
||||||
);
|
);
|
||||||
|
|
||||||
final salt = Format.uint8listToString(
|
final salt = Format.uint8listToString(
|
||||||
|
@ -105,13 +120,29 @@ class _CompleteReshareConfigViewState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.read(pFrostResharingData).myName = frostInfo.myName;
|
ref.read(pFrostResharingData).myName = myName;
|
||||||
ref.read(pFrostResharingData).resharerConfig = config;
|
ref.read(pFrostResharingData).resharerRConfig = Frost.encodeRConfig(
|
||||||
|
config,
|
||||||
|
widget.resharers,
|
||||||
|
);
|
||||||
|
|
||||||
|
final wallet =
|
||||||
|
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
||||||
|
|
||||||
|
ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
|
info: (
|
||||||
|
walletName: wallet.info.name,
|
||||||
|
frostCurrency: wallet.cryptoCurrency,
|
||||||
|
),
|
||||||
|
walletId: wallet.walletId,
|
||||||
|
stepRoutes: FrostRouteGenerator.initiateReshareStepRoutes,
|
||||||
|
parentNav: Navigator.of(context),
|
||||||
|
frostInterruptionDialogType: FrostInterruptionDialogType.resharing,
|
||||||
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
DisplayReshareConfigView.routeName,
|
FrostStepScaffold.routeName,
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -152,18 +183,29 @@ class _CompleteReshareConfigViewState
|
||||||
return "At least two participants required";
|
return "At least two participants required";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controllers.length != partsCount) {
|
final newParticipants = controllers.map((e) => e.text.trim()).toList();
|
||||||
|
|
||||||
|
if (newParticipants.contains(myName)) {
|
||||||
|
return "Using your own name should be done using the checkbox to include"
|
||||||
|
" yourself";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_includeMeInReshare) {
|
||||||
|
newParticipants.add(myName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newParticipants.length != partsCount) {
|
||||||
return "Participants count error";
|
return "Participants count error";
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasEmptyParticipants = controllers
|
final hasEmptyParticipants = newParticipants
|
||||||
.map((e) => e.text.isEmpty)
|
.map((e) => e.trim().isEmpty)
|
||||||
.reduce((value, element) => value |= element);
|
.reduce((value, element) => value |= element);
|
||||||
if (hasEmptyParticipants) {
|
if (hasEmptyParticipants) {
|
||||||
return "Participants must not be empty";
|
return "Participants must not be empty";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controllers.length != controllers.map((e) => e.text).toSet().length) {
|
if (newParticipants.length != newParticipants.toSet().length) {
|
||||||
return "Duplicate participant name found";
|
return "Duplicate participant name found";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,8 +213,12 @@ class _CompleteReshareConfigViewState
|
||||||
}
|
}
|
||||||
|
|
||||||
void _participantsCountChanged(String newValue) {
|
void _participantsCountChanged(String newValue) {
|
||||||
final count = int.tryParse(newValue);
|
int? count = int.tryParse(newValue);
|
||||||
if (count != null) {
|
if (count != null) {
|
||||||
|
if (_includeMeInReshare) {
|
||||||
|
count = max(0, count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (count > _participantsCount) {
|
if (count > _participantsCount) {
|
||||||
for (int i = _participantsCount; i < count; i++) {
|
for (int i = _participantsCount; i < count; i++) {
|
||||||
controllers.add(TextEditingController());
|
controllers.add(TextEditingController());
|
||||||
|
@ -192,6 +238,17 @@ class _CompleteReshareConfigViewState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final frostInfo = ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.frostWalletInfo
|
||||||
|
.getByWalletIdSync(widget.walletId)!;
|
||||||
|
myName = frostInfo.myName;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_newThresholdController.dispose();
|
_newThresholdController.dispose();
|
||||||
|
@ -231,7 +288,7 @@ class _CompleteReshareConfigViewState
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Modify Participants",
|
"Edit group details",
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -258,11 +315,62 @@ class _CompleteReshareConfigViewState
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: Util.isDesktop
|
||||||
|
? CrossAxisAlignment.start
|
||||||
|
: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_includeMeInReshare = !_includeMeInReshare;
|
||||||
|
});
|
||||||
|
_participantsCountChanged(_newParticipantsCountController.text);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 26,
|
||||||
|
child: Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
value: _includeMeInReshare,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(
|
||||||
|
() => _includeMeInReshare = value == true,
|
||||||
|
);
|
||||||
|
_participantsCountChanged(
|
||||||
|
_newParticipantsCountController.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"I will be a signer in the new config",
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
"New threshold",
|
"New threshold",
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
|
@ -271,13 +379,32 @@ class _CompleteReshareConfigViewState
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
controller: _newThresholdController,
|
controller: _newThresholdController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter number of signatures",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Text(
|
||||||
|
"Enter number of signatures required for fund management.",
|
||||||
|
style: STextStyles.w500_12(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Number of participants",
|
"New number of participants",
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
|
@ -287,6 +414,23 @@ class _CompleteReshareConfigViewState
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
controller: _newParticipantsCountController,
|
controller: _newParticipantsCountController,
|
||||||
onChanged: _participantsCountChanged,
|
onChanged: _participantsCountChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter number of participants",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Text(
|
||||||
|
"The number of participants must be equal to or less than the"
|
||||||
|
" number of required signatures.",
|
||||||
|
style: STextStyles.w500_12(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
|
@ -294,12 +438,26 @@ class _CompleteReshareConfigViewState
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
"Participants",
|
"Participants",
|
||||||
style: STextStyles.label(context),
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
|
if (controllers.isNotEmpty)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Text(
|
||||||
|
"Type each name in one word without spaces.",
|
||||||
|
style: STextStyles.w500_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (controllers.isNotEmpty)
|
if (controllers.isNotEmpty)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -310,6 +468,10 @@ class _CompleteReshareConfigViewState
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controllers[i],
|
controller: controllers[i],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter name",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
|
@ -1,11 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
||||||
|
@ -15,9 +14,10 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
final class BeginReshareConfigView extends ConsumerStatefulWidget {
|
final class InitiateResharingView extends ConsumerStatefulWidget {
|
||||||
const BeginReshareConfigView({
|
const InitiateResharingView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
});
|
});
|
||||||
|
@ -27,16 +27,18 @@ final class BeginReshareConfigView extends ConsumerStatefulWidget {
|
||||||
final String walletId;
|
final String walletId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<BeginReshareConfigView> createState() =>
|
ConsumerState<InitiateResharingView> createState() =>
|
||||||
_BeginReshareConfigViewState();
|
_BeginReshareConfigViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BeginReshareConfigViewState
|
class _BeginReshareConfigViewState
|
||||||
extends ConsumerState<BeginReshareConfigView> {
|
extends ConsumerState<InitiateResharingView> {
|
||||||
|
late final String myName;
|
||||||
late final int currentThreshold;
|
late final int currentThreshold;
|
||||||
late final List<String> currentParticipants;
|
late final List<String> originalParticipants;
|
||||||
|
late final List<String> currentParticipantsWithoutMe;
|
||||||
|
|
||||||
final Map<String, int> pFrostResharersMap = {};
|
final Set<String> selectedParticipants = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -50,7 +52,14 @@ class _BeginReshareConfigViewState
|
||||||
.getByWalletIdSync(widget.walletId)!;
|
.getByWalletIdSync(widget.walletId)!;
|
||||||
|
|
||||||
currentThreshold = frostInfo.threshold;
|
currentThreshold = frostInfo.threshold;
|
||||||
currentParticipants = frostInfo.participants;
|
originalParticipants = frostInfo.participants.toList(growable: false);
|
||||||
|
currentParticipantsWithoutMe = originalParticipants.toList();
|
||||||
|
|
||||||
|
// sanity check (should never actually fail, but very bad if it does)
|
||||||
|
assert(originalParticipants.length == currentParticipantsWithoutMe.length);
|
||||||
|
|
||||||
|
myName = frostInfo.myName;
|
||||||
|
currentParticipantsWithoutMe.remove(myName);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -83,10 +92,10 @@ class _BeginReshareConfigViewState
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// title: Text(
|
title: Text(
|
||||||
// "Modify Participants",
|
"Initiate resharing",
|
||||||
// style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
// ),
|
),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
@ -113,33 +122,48 @@ class _BeginReshareConfigViewState
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
RoundedWhiteContainer(
|
||||||
"Select participants for resharing",
|
child: Column(
|
||||||
style: STextStyles.label(context),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select group members who will participate in resharing.",
|
||||||
|
style: STextStyles.w600_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"You must have the threshold number of members (including you) to initiate resharing.",
|
||||||
|
style: STextStyles.w600_12(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < currentParticipants.length; i++)
|
for (int i = 0; i < currentParticipantsWithoutMe.length; i++)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 10,
|
top: 10,
|
||||||
),
|
),
|
||||||
child: RawMaterialButton(
|
child: RoundedWhiteContainer(
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
padding: EdgeInsets.zero,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (pFrostResharersMap[currentParticipants[i]] ==
|
if (selectedParticipants
|
||||||
null) {
|
.contains(currentParticipantsWithoutMe[i])) {
|
||||||
pFrostResharersMap[currentParticipants[i]] = i;
|
selectedParticipants
|
||||||
|
.remove(currentParticipantsWithoutMe[i]);
|
||||||
} else {
|
} else {
|
||||||
pFrostResharersMap.remove(currentParticipants[i]);
|
selectedParticipants
|
||||||
|
.add(currentParticipantsWithoutMe[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -150,16 +174,15 @@ class _BeginReshareConfigViewState
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
value: pFrostResharersMap[
|
value: selectedParticipants
|
||||||
currentParticipants[i]] ==
|
.contains(currentParticipantsWithoutMe[i]),
|
||||||
i,
|
onChanged: (_) {},
|
||||||
onChanged: (bool? value) {},
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 10,
|
width: 10,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
currentParticipants[i],
|
currentParticipantsWithoutMe[i],
|
||||||
style: STextStyles.itemSubtitle12(context),
|
style: STextStyles.itemSubtitle12(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -174,16 +197,55 @@ class _BeginReshareConfigViewState
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Required members",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
// +1 is included as the initiator who will also take part
|
||||||
|
"${selectedParticipants.length + 1} / $currentThreshold",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: selectedParticipants.length + 1 >= currentThreshold
|
||||||
|
? Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorGreen
|
||||||
|
: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorRed,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Continue",
|
label: "Continue",
|
||||||
enabled: pFrostResharersMap.length >= currentThreshold,
|
// +1 is included as the initiator who will also take part
|
||||||
|
enabled: selectedParticipants.length + 1 >= currentThreshold,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
// include self now
|
||||||
|
selectedParticipants.add(myName);
|
||||||
|
|
||||||
|
final Map<String, int> resharers = {};
|
||||||
|
|
||||||
|
for (final name in selectedParticipants) {
|
||||||
|
resharers[name] = originalParticipants.indexOf(name);
|
||||||
|
}
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
CompleteReshareConfigView.routeName,
|
CompleteReshareConfigView.routeName,
|
||||||
arguments: (
|
arguments: (
|
||||||
walletId: widget.walletId,
|
walletId: widget.walletId,
|
||||||
resharers:
|
resharers: resharers,
|
||||||
pFrostResharersMap.values.toList(growable: false),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
|
@ -1,437 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/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 FinishResharingView extends ConsumerStatefulWidget {
|
|
||||||
const FinishResharingView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/finishResharingView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<FinishResharingView> createState() =>
|
|
||||||
_FinishResharingViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FinishResharingViewState extends ConsumerState<FinishResharingView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<int> resharerIndexes;
|
|
||||||
late final String myName;
|
|
||||||
late final int? myResharerIndexIndex;
|
|
||||||
late final String? myResharerComplete;
|
|
||||||
late final bool amOutgoingParticipant;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (amOutgoingParticipant) {
|
|
||||||
ref.read(pFrostResharingData).reset();
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(
|
|
||||||
Util.isDesktop ? DesktopWalletView.routeName : WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// collect resharer completes strings and insert my own at the correct index
|
|
||||||
final resharerCompletes = controllers.map((e) => e.text).toList();
|
|
||||||
if (myResharerIndexIndex != null && myResharerComplete != null) {
|
|
||||||
resharerCompletes.insert(myResharerIndexIndex!, myResharerComplete!);
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = Frost.finishReshared(
|
|
||||||
prior: ref.read(pFrostResharingData).startResharedData!.prior.ref,
|
|
||||||
resharerCompletes: resharerCompletes,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).newWalletData = data;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
VerifyUpdatedWalletView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
final amNewParticipant =
|
|
||||||
ref.read(pFrostResharingData).startResharerData == null &&
|
|
||||||
ref.read(pFrostResharingData).incompleteWallet != null &&
|
|
||||||
ref.read(pFrostResharingData).incompleteWallet?.walletId ==
|
|
||||||
widget.walletId;
|
|
||||||
|
|
||||||
myName = ref.read(pFrostResharingData).myName!;
|
|
||||||
|
|
||||||
resharerIndexes = ref.read(pFrostResharingData).configData!.resharers;
|
|
||||||
|
|
||||||
if (amNewParticipant) {
|
|
||||||
myResharerComplete = null;
|
|
||||||
myResharerIndexIndex = null;
|
|
||||||
amOutgoingParticipant = false;
|
|
||||||
} else {
|
|
||||||
myResharerComplete = ref.read(pFrostResharingData).resharerComplete!;
|
|
||||||
|
|
||||||
final frostInfo = ref
|
|
||||||
.read(mainDBProvider)
|
|
||||||
.isar
|
|
||||||
.frostWalletInfo
|
|
||||||
.getByWalletIdSync(widget.walletId)!;
|
|
||||||
final myOldIndex =
|
|
||||||
frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!);
|
|
||||||
|
|
||||||
myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex);
|
|
||||||
if (myResharerIndexIndex! >= 0) {
|
|
||||||
// remove my name for now as we don't need a text field for it
|
|
||||||
resharerIndexes.removeAt(myResharerIndexIndex!);
|
|
||||||
}
|
|
||||||
|
|
||||||
amOutgoingParticipant = !ref
|
|
||||||
.read(pFrostResharingData)
|
|
||||||
.configData!
|
|
||||||
.newParticipants
|
|
||||||
.contains(ref.read(pFrostResharingData).myName!);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Resharer completes",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (myResharerComplete != null)
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: myResharerComplete!,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (myResharerComplete != null) const _Div(),
|
|
||||||
if (myResharerComplete != null)
|
|
||||||
DetailItem(
|
|
||||||
title: "My resharer complete",
|
|
||||||
detail: myResharerComplete!,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: myResharerComplete!,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: myResharerComplete!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!amOutgoingParticipant) const _Div(),
|
|
||||||
if (!amOutgoingParticipant)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostEncryptionKeyTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter index "
|
|
||||||
"${resharerIndexes[i]}"
|
|
||||||
"'s resharer complete",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Encryption Key Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostEncryptionKeyClearButtonKey_$i"),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Encryption Key Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostEncryptionKeyPasteButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel: "Scan QR Button. "
|
|
||||||
"Opens Camera For Scanning QR Code.",
|
|
||||||
key: Key("frostScanQrButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions "
|
|
||||||
"while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: amOutgoingParticipant ? "Exit" : "Complete",
|
|
||||||
enabled: amOutgoingParticipant ||
|
|
||||||
!fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,214 +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/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/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/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
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 DisplayReshareConfigView extends ConsumerStatefulWidget {
|
|
||||||
const DisplayReshareConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/displayReshareConfigView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<DisplayReshareConfigView> createState() =>
|
|
||||||
_DisplayReshareConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DisplayReshareConfigViewState
|
|
||||||
extends ConsumerState<DisplayReshareConfigView> {
|
|
||||||
late final bool iAmInvolved;
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
|
|
||||||
final serializedKeys = await wallet.getSerializedKeys();
|
|
||||||
if (mounted) {
|
|
||||||
final result = Frost.beginResharer(
|
|
||||||
serializedKeys: serializedKeys!,
|
|
||||||
config: ref.read(pFrostResharingData).resharerConfig!,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).startResharerData = result;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
BeginResharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
|
||||||
final frostInfo = ref
|
|
||||||
.read(mainDBProvider)
|
|
||||||
.isar
|
|
||||||
.frostWalletInfo
|
|
||||||
.getByWalletIdSync(widget.walletId)!;
|
|
||||||
|
|
||||||
final myOldIndex = frostInfo.participants.indexOf(frostInfo.myName);
|
|
||||||
|
|
||||||
iAmInvolved = ref
|
|
||||||
.read(pFrostResharingData)
|
|
||||||
.configData!
|
|
||||||
.resharers
|
|
||||||
.contains(myOldIndex);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Resharer config",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: ref.watch(pFrostResharingData).resharerConfig!,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 32,
|
|
||||||
),
|
|
||||||
DetailItem(
|
|
||||||
title: "Config",
|
|
||||||
detail: ref.watch(pFrostResharingData).resharerConfig!,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: ref.watch(pFrostResharingData).resharerConfig!,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: ref.watch(pFrostResharingData).resharerConfig!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: Util.isDesktop ? 64 : 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop)
|
|
||||||
const Spacer(
|
|
||||||
flex: 2,
|
|
||||||
),
|
|
||||||
if (iAmInvolved)
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Start resharing",
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,338 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:frostdart/frostdart.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/format.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class ImportReshareConfigView extends ConsumerStatefulWidget {
|
|
||||||
const ImportReshareConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/importReshareConfigView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<ImportReshareConfigView> createState() =>
|
|
||||||
_ImportReshareConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ImportReshareConfigViewState
|
|
||||||
extends ConsumerState<ImportReshareConfigView> {
|
|
||||||
late final TextEditingController configFieldController;
|
|
||||||
late final FocusNode configFocusNode;
|
|
||||||
|
|
||||||
bool _configEmpty = true;
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
|
||||||
final frostInfo = ref
|
|
||||||
.read(mainDBProvider)
|
|
||||||
.isar
|
|
||||||
.frostWalletInfo
|
|
||||||
.getByWalletIdSync(widget.walletId)!;
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).reset();
|
|
||||||
ref.read(pFrostResharingData).myName = frostInfo.myName;
|
|
||||||
ref.read(pFrostResharingData).resharerConfig = configFieldController.text;
|
|
||||||
|
|
||||||
String? salt;
|
|
||||||
try {
|
|
||||||
salt = Format.uint8listToString(
|
|
||||||
resharerSalt(
|
|
||||||
resharerConfig: ref.read(pFrostResharingData).resharerConfig!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
throw Exception("Bad resharer config");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frostInfo.knownSalts.contains(salt)) {
|
|
||||||
throw Exception("Duplicate config salt");
|
|
||||||
} else {
|
|
||||||
final salts = frostInfo.knownSalts;
|
|
||||||
salts.add(salt);
|
|
||||||
final mainDB = ref.read(mainDBProvider);
|
|
||||||
await mainDB.isar.writeTxn(() async {
|
|
||||||
final info = frostInfo;
|
|
||||||
await mainDB.isar.frostWalletInfo.delete(info.id);
|
|
||||||
await mainDB.isar.frostWalletInfo.put(
|
|
||||||
info.copyWith(knownSalts: salts),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final serializedKeys = await ref.read(secureStoreProvider).read(
|
|
||||||
key: "{${widget.walletId}}_serializedFROSTKeys",
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
final result = Frost.beginResharer(
|
|
||||||
serializedKeys: serializedKeys!,
|
|
||||||
config: ref.read(pFrostResharingData).resharerConfig!,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).startResharerData = result;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
BeginResharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
configFieldController = TextEditingController();
|
|
||||||
configFocusNode = FocusNode();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
configFieldController.dispose();
|
|
||||||
configFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: const DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: const AppBarBackButton(),
|
|
||||||
title: Text(
|
|
||||||
"Import FROST reshare config",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: const Key("frConfigTextFieldKey"),
|
|
||||||
controller: configFieldController,
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
focusNode: configFocusNode,
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter config",
|
|
||||||
configFocusNode,
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: _configEmpty
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!_configEmpty
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Config Field.",
|
|
||||||
key: const Key("frConfigClearButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
configFieldController.text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Config Field Input.",
|
|
||||||
key: const Key("frConfigPasteButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
configFieldController.text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: _configEmpty
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (_configEmpty)
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
|
||||||
key: const Key("frConfigScanQrButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult = await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
configFieldController.text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Start resharing",
|
|
||||||
enabled: !_configEmpty,
|
|
||||||
onPressed: () async {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _onPressed();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/isar/models/frost_wallet_info.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class BeginResharingView extends ConsumerStatefulWidget {
|
|
||||||
const BeginResharingView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/beginResharingView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<BeginResharingView> createState() => _BeginResharingViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BeginResharingViewState extends ConsumerState<BeginResharingView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<int> resharerIndexes;
|
|
||||||
late final int myResharerIndexIndex;
|
|
||||||
late final String myResharerStart;
|
|
||||||
late final bool amOutgoingParticipant;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!amOutgoingParticipant) {
|
|
||||||
// collect resharer strings
|
|
||||||
final resharerStarts = controllers.map((e) => e.text).toList();
|
|
||||||
if (myResharerIndexIndex >= 0) {
|
|
||||||
// only insert my own at the correct index if I am a resharer
|
|
||||||
resharerStarts.insert(myResharerIndexIndex, myResharerStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = Frost.beginReshared(
|
|
||||||
myName: ref.read(pFrostResharingData).myName!,
|
|
||||||
resharerConfig: ref.read(pFrostResharingData).resharerConfig!,
|
|
||||||
resharerStarts: resharerStarts,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).startResharedData = result;
|
|
||||||
}
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
ContinueResharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// TODO: optimize this by creating watcher providers (similar to normal WalletInfo)
|
|
||||||
final frostInfo = ref
|
|
||||||
.read(mainDBProvider)
|
|
||||||
.isar
|
|
||||||
.frostWalletInfo
|
|
||||||
.getByWalletIdSync(widget.walletId)!;
|
|
||||||
final myOldIndex =
|
|
||||||
frostInfo.participants.indexOf(ref.read(pFrostResharingData).myName!);
|
|
||||||
|
|
||||||
myResharerStart =
|
|
||||||
ref.read(pFrostResharingData).startResharerData!.resharerStart;
|
|
||||||
|
|
||||||
resharerIndexes = ref.read(pFrostResharingData).configData!.resharers;
|
|
||||||
myResharerIndexIndex = resharerIndexes.indexOf(myOldIndex);
|
|
||||||
if (myResharerIndexIndex >= 0) {
|
|
||||||
// remove my name for now as we don't need a text field for it
|
|
||||||
resharerIndexes.removeAt(myResharerIndexIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
amOutgoingParticipant = !ref
|
|
||||||
.read(pFrostResharingData)
|
|
||||||
.configData!
|
|
||||||
.newParticipants
|
|
||||||
.contains(ref.read(pFrostResharingData).myName!);
|
|
||||||
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: Util.isDesktop
|
|
||||||
? DesktopWalletView.routeName
|
|
||||||
: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: DesktopWalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Resharers",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: myResharerStart,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My resharer",
|
|
||||||
detail: myResharerStart,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: myResharerStart,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: myResharerStart,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostResharerTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter index "
|
|
||||||
"${resharerIndexes[i]}"
|
|
||||||
"'s resharer",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Resharer Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostResharerClearButtonKey_$i"),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Resharer Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostResharerPasteButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel: "Scan QR Button. "
|
|
||||||
"Opens Camera For Scanning QR Code.",
|
|
||||||
key: Key(
|
|
||||||
"frostCommitmentsScanQrButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions "
|
|
||||||
"while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Continue",
|
|
||||||
enabled: amOutgoingParticipant ||
|
|
||||||
!fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,429 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
|
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class ContinueResharingView extends ConsumerStatefulWidget {
|
|
||||||
const ContinueResharingView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/continueResharingView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<ContinueResharingView> createState() =>
|
|
||||||
_ContinueResharingViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ContinueResharingViewState extends ConsumerState<ContinueResharingView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<String> newParticipants;
|
|
||||||
late final int myIndex;
|
|
||||||
late final String? myEncryptionKey;
|
|
||||||
late final bool amOutgoingParticipant;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// collect encryptionKeys strings and insert my own at the correct index
|
|
||||||
final encryptionKeys = controllers.map((e) => e.text).toList();
|
|
||||||
if (!amOutgoingParticipant) {
|
|
||||||
encryptionKeys.insert(myIndex, myEncryptionKey!);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = Frost.finishResharer(
|
|
||||||
machine: ref.read(pFrostResharingData).startResharerData!.machine.ref,
|
|
||||||
encryptionKeysOfResharedTo: encryptionKeys,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).resharerComplete = result;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
FinishResharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
myEncryptionKey =
|
|
||||||
ref.read(pFrostResharingData).startResharedData?.resharedStart;
|
|
||||||
|
|
||||||
newParticipants = ref.read(pFrostResharingData).configData!.newParticipants;
|
|
||||||
myIndex = newParticipants.indexOf(ref.read(pFrostResharingData).myName!);
|
|
||||||
|
|
||||||
if (myIndex >= 0) {
|
|
||||||
// remove my name for now as we don't need a text field for it
|
|
||||||
newParticipants.removeAt(myIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (myEncryptionKey == null && myIndex == -1) {
|
|
||||||
amOutgoingParticipant = true;
|
|
||||||
} else if (myEncryptionKey != null && myIndex >= 0) {
|
|
||||||
amOutgoingParticipant = false;
|
|
||||||
} else {
|
|
||||||
throw Exception("Invalid resharing state");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < newParticipants.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: Util.isDesktop
|
|
||||||
? DesktopWalletView.routeName
|
|
||||||
: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: DesktopWalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: WalletView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Encryption keys",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (!amOutgoingParticipant)
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: myEncryptionKey!,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!amOutgoingParticipant) const _Div(),
|
|
||||||
if (!amOutgoingParticipant)
|
|
||||||
DetailItem(
|
|
||||||
title: "My encryption key",
|
|
||||||
detail: myEncryptionKey!,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: myEncryptionKey!,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: myEncryptionKey!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!amOutgoingParticipant) const _Div(),
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < newParticipants.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostEncryptionKeyTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter "
|
|
||||||
"${newParticipants[i]}"
|
|
||||||
"'s encryption key",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Encryption Key Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostEncryptionKeyClearButtonKey_$i"),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Encryption Key Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostEncryptionKeyPasteButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel: "Scan QR Button. "
|
|
||||||
"Opens Camera For Scanning QR Code.",
|
|
||||||
key: Key(
|
|
||||||
"frostCommitmentsScanQrButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions "
|
|
||||||
"while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Continue",
|
|
||||||
enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
|
||||||
|
|
||||||
import 'package:stackwallet/pages/frost_mascot.dart';
|
|
||||||
|
|
||||||
class NewContinueSharingView extends ConsumerStatefulWidget {
|
|
||||||
const NewContinueSharingView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/NewContinueSharingView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<NewContinueSharingView> createState() =>
|
|
||||||
_NewContinueSharingViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewContinueSharingViewState
|
|
||||||
extends ConsumerState<NewContinueSharingView> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName:
|
|
||||||
Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: DesktopHomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
trailing: FrostMascot(
|
|
||||||
title: 'Lorem ipsum',
|
|
||||||
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: HomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Encryption keys",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 220,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
QrImageView(
|
|
||||||
data: ref
|
|
||||||
.watch(pFrostResharingData)
|
|
||||||
.startResharedData!
|
|
||||||
.resharedStart,
|
|
||||||
size: 220,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.background,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "My encryption key",
|
|
||||||
detail: ref
|
|
||||||
.watch(pFrostResharingData)
|
|
||||||
.startResharedData!
|
|
||||||
.resharedStart,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: ref
|
|
||||||
.watch(pFrostResharingData)
|
|
||||||
.startResharedData!
|
|
||||||
.resharedStart,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: ref
|
|
||||||
.watch(pFrostResharingData)
|
|
||||||
.startResharedData!
|
|
||||||
.resharedStart,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Continue",
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
FinishResharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,431 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/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/isar/models/wallet_info.dart';
|
|
||||||
import 'package:stackwallet/wallets/models/incomplete_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/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 NewImportResharerConfigView extends ConsumerStatefulWidget {
|
|
||||||
const NewImportResharerConfigView({
|
|
||||||
super.key,
|
|
||||||
required this.walletName,
|
|
||||||
required this.coin,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/newImportResharerConfigView";
|
|
||||||
|
|
||||||
final String walletName;
|
|
||||||
final Coin coin;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<NewImportResharerConfigView> createState() =>
|
|
||||||
_NewImportResharerConfigViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewImportResharerConfigViewState
|
|
||||||
extends ConsumerState<NewImportResharerConfigView> {
|
|
||||||
late final TextEditingController myNameFieldController, configFieldController;
|
|
||||||
late final FocusNode myNameFocusNode, configFocusNode;
|
|
||||||
|
|
||||||
bool _nameEmpty = true, _configEmpty = true;
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
|
|
||||||
Future<IncompleteFrostWallet> _createWallet() async {
|
|
||||||
final info = WalletInfo.createNew(
|
|
||||||
name: widget.walletName,
|
|
||||||
coin: widget.coin,
|
|
||||||
);
|
|
||||||
|
|
||||||
final wallet = IncompleteFrostWallet();
|
|
||||||
wallet.info = info;
|
|
||||||
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
myNameFieldController = TextEditingController();
|
|
||||||
configFieldController = TextEditingController();
|
|
||||||
myNameFocusNode = FocusNode();
|
|
||||||
configFocusNode = FocusNode();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
myNameFieldController.dispose();
|
|
||||||
configFieldController.dispose();
|
|
||||||
myNameFocusNode.dispose();
|
|
||||||
configFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.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<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Import FROST reshare config",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: const Key("frMyNameTextFieldKey"),
|
|
||||||
controller: myNameFieldController,
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
_nameEmpty = myNameFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
focusNode: myNameFocusNode,
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"My name",
|
|
||||||
myNameFocusNode,
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: _nameEmpty
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!_nameEmpty
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Config Field.",
|
|
||||||
key: const Key("frMyNameClearButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
myNameFieldController.text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_nameEmpty = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Name Field.",
|
|
||||||
key: const Key("frMyNamePasteButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
myNameFieldController.text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_nameEmpty =
|
|
||||||
myNameFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: _nameEmpty
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: const Key("frConfigTextFieldKey"),
|
|
||||||
controller: configFieldController,
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
focusNode: configFocusNode,
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter config",
|
|
||||||
configFocusNode,
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: _configEmpty
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!_configEmpty
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Config Field.",
|
|
||||||
key: const Key("frConfigClearButtonKey"),
|
|
||||||
onTap: () {
|
|
||||||
configFieldController.text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Config Field Input.",
|
|
||||||
key: const Key("frConfigPasteButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
configFieldController.text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: _configEmpty
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (_configEmpty)
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
|
||||||
key: const Key("frConfigScanQrButtonKey"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult = await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
configFieldController.text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_configEmpty =
|
|
||||||
configFieldController.text.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Start",
|
|
||||||
enabled: !_nameEmpty && !_configEmpty,
|
|
||||||
onPressed: () async {
|
|
||||||
if (FocusScope.of(context).hasFocus) {
|
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
}
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
ref.read(pFrostResharingData).reset();
|
|
||||||
ref.read(pFrostResharingData).myName =
|
|
||||||
myNameFieldController.text;
|
|
||||||
ref.read(pFrostResharingData).resharerConfig =
|
|
||||||
configFieldController.text;
|
|
||||||
|
|
||||||
if (!ref
|
|
||||||
.read(pFrostResharingData)
|
|
||||||
.configData!
|
|
||||||
.newParticipants
|
|
||||||
.contains(ref.read(pFrostResharingData).myName!)) {
|
|
||||||
ref.read(pFrostResharingData).reset();
|
|
||||||
return await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "My name not found in config participants",
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception? ex;
|
|
||||||
final wallet = await showLoading(
|
|
||||||
whileFuture: _createWallet(),
|
|
||||||
context: context,
|
|
||||||
message: "Setting up wallet",
|
|
||||||
isDesktop: Util.isDesktop,
|
|
||||||
onException: (e) => ex = e,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ex != null) {
|
|
||||||
throw ex!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
ref.read(pFrostResharingData).incompleteWallet = wallet!;
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
NewStartResharingView.routeName,
|
|
||||||
arguments: wallet.walletId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,373 +0,0 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/services/frost.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_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';
|
|
||||||
|
|
||||||
import 'package:stackwallet/pages/frost_mascot.dart';
|
|
||||||
|
|
||||||
class NewStartResharingView extends ConsumerStatefulWidget {
|
|
||||||
const NewStartResharingView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/newStartResharingView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<NewStartResharingView> createState() =>
|
|
||||||
_NewStartResharingViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NewStartResharingViewState extends ConsumerState<NewStartResharingView> {
|
|
||||||
final List<TextEditingController> controllers = [];
|
|
||||||
final List<FocusNode> focusNodes = [];
|
|
||||||
|
|
||||||
late final List<int> resharerIndexes;
|
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// collect resharer strings
|
|
||||||
final resharerStarts = controllers.map((e) => e.text).toList();
|
|
||||||
|
|
||||||
final result = Frost.beginReshared(
|
|
||||||
myName: ref.read(pFrostResharingData).myName!,
|
|
||||||
resharerConfig: ref.read(pFrostResharingData).resharerConfig!,
|
|
||||||
resharerStarts: resharerStarts,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pFrostResharingData).startResharedData = result;
|
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
|
||||||
NewContinueSharingView.routeName,
|
|
||||||
arguments: widget.walletId,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
resharerIndexes = ref.read(pFrostResharingData).configData!.resharers;
|
|
||||||
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++) {
|
|
||||||
controllers.add(TextEditingController());
|
|
||||||
focusNodes.add(FocusNode());
|
|
||||||
fieldIsEmptyFlags.add(true);
|
|
||||||
}
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (int i = 0; i < controllers.length; i++) {
|
|
||||||
controllers[i].dispose();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < focusNodes.length; i++) {
|
|
||||||
focusNodes[i].dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName:
|
|
||||||
Util.isDesktop ? DesktopHomeView.routeName : HomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: DesktopHomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
trailing: FrostMascot(
|
|
||||||
title: 'Lorem ipsum',
|
|
||||||
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => const FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: HomeView.routeName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
"Resharers",
|
|
||||||
style: STextStyles.navBarTitle(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < resharerIndexes.length; i++)
|
|
||||||
Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
Constants.size.circularBorderRadius,
|
|
||||||
),
|
|
||||||
child: TextField(
|
|
||||||
key: Key("frostResharerTextFieldKey_$i"),
|
|
||||||
controller: controllers[i],
|
|
||||||
focusNode: focusNodes[i],
|
|
||||||
readOnly: false,
|
|
||||||
autocorrect: false,
|
|
||||||
enableSuggestions: false,
|
|
||||||
style: STextStyles.field(context),
|
|
||||||
onChanged: (_) {
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i].text.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: standardInputDecoration(
|
|
||||||
"Enter index "
|
|
||||||
"${resharerIndexes[i]}"
|
|
||||||
"'s resharer",
|
|
||||||
focusNodes[i],
|
|
||||||
context,
|
|
||||||
).copyWith(
|
|
||||||
contentPadding: const EdgeInsets.only(
|
|
||||||
left: 16,
|
|
||||||
top: 6,
|
|
||||||
bottom: 8,
|
|
||||||
right: 5,
|
|
||||||
),
|
|
||||||
suffixIcon: Padding(
|
|
||||||
padding: fieldIsEmptyFlags[i]
|
|
||||||
? const EdgeInsets.only(right: 8)
|
|
||||||
: const EdgeInsets.only(right: 0),
|
|
||||||
child: UnconstrainedBox(
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceAround,
|
|
||||||
children: [
|
|
||||||
!fieldIsEmptyFlags[i]
|
|
||||||
? TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Clear Button. Clears The Resharer Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostResharerClearButtonKey_$i"),
|
|
||||||
onTap: () {
|
|
||||||
controllers[i].text = "";
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: const XIcon(),
|
|
||||||
)
|
|
||||||
: TextFieldIconButton(
|
|
||||||
semanticsLabel:
|
|
||||||
"Paste Button. Pastes From Clipboard To Resharer Field Input.",
|
|
||||||
key: Key(
|
|
||||||
"frostResharerPasteButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
final ClipboardData? data =
|
|
||||||
await Clipboard.getData(
|
|
||||||
Clipboard.kTextPlain);
|
|
||||||
if (data?.text != null &&
|
|
||||||
data!.text!.isNotEmpty) {
|
|
||||||
controllers[i].text =
|
|
||||||
data.text!.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: fieldIsEmptyFlags[i]
|
|
||||||
? const ClipboardIcon()
|
|
||||||
: const XIcon(),
|
|
||||||
),
|
|
||||||
if (fieldIsEmptyFlags[i])
|
|
||||||
TextFieldIconButton(
|
|
||||||
semanticsLabel: "Scan QR Button. "
|
|
||||||
"Opens Camera For Scanning QR Code.",
|
|
||||||
key: Key(
|
|
||||||
"frostCommitmentsScanQrButtonKey_$i"),
|
|
||||||
onTap: () async {
|
|
||||||
try {
|
|
||||||
if (FocusScope.of(context)
|
|
||||||
.hasFocus) {
|
|
||||||
FocusScope.of(context)
|
|
||||||
.unfocus();
|
|
||||||
await Future<void>.delayed(
|
|
||||||
const Duration(
|
|
||||||
milliseconds: 75));
|
|
||||||
}
|
|
||||||
|
|
||||||
final qrResult =
|
|
||||||
await BarcodeScanner.scan();
|
|
||||||
|
|
||||||
controllers[i].text =
|
|
||||||
qrResult.rawContent;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
fieldIsEmptyFlags[i] =
|
|
||||||
controllers[i]
|
|
||||||
.text
|
|
||||||
.isEmpty;
|
|
||||||
});
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Failed to get camera permissions "
|
|
||||||
"while trying to scan qr code: $e\n$s",
|
|
||||||
level: LogLevel.Warning,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const QrCodeIcon(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Continue",
|
|
||||||
enabled: !fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,315 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|
||||||
import 'package:stackwallet/providers/global/node_service_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
|
|
||||||
class VerifyUpdatedWalletView extends ConsumerStatefulWidget {
|
|
||||||
const VerifyUpdatedWalletView({
|
|
||||||
super.key,
|
|
||||||
required this.walletId,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/verifyUpdatedWalletView";
|
|
||||||
|
|
||||||
final String walletId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<VerifyUpdatedWalletView> createState() =>
|
|
||||||
_VerifyUpdatedWalletViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _VerifyUpdatedWalletViewState
|
|
||||||
extends ConsumerState<VerifyUpdatedWalletView> {
|
|
||||||
late final String config;
|
|
||||||
late final String serializedKeys;
|
|
||||||
late final String reshareId;
|
|
||||||
|
|
||||||
late final bool isNew;
|
|
||||||
|
|
||||||
bool _buttonLock = false;
|
|
||||||
Future<void> _onPressed() async {
|
|
||||||
if (_buttonLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_buttonLock = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Exception? ex;
|
|
||||||
|
|
||||||
final BitcoinFrostWallet wallet;
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
wallet = await ref
|
|
||||||
.read(pFrostResharingData)
|
|
||||||
.incompleteWallet!
|
|
||||||
.toBitcoinFrostWallet(
|
|
||||||
mainDB: ref.read(mainDBProvider),
|
|
||||||
secureStorageInterface: ref.read(secureStoreProvider),
|
|
||||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
|
||||||
prefs: ref.read(prefsChangeNotifierProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
await wallet.info.setMnemonicVerified(
|
|
||||||
isar: ref.read(mainDBProvider).isar,
|
|
||||||
);
|
|
||||||
|
|
||||||
ref.read(pWallets).addWallet(wallet);
|
|
||||||
} else {
|
|
||||||
wallet =
|
|
||||||
ref.read(pWallets).getWallet(widget.walletId) as BitcoinFrostWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
await showLoading(
|
|
||||||
whileFuture: wallet.updateWithResharedData(
|
|
||||||
serializedKeys: serializedKeys,
|
|
||||||
multisigConfig: config,
|
|
||||||
isNewWallet: isNew,
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
message: isNew ? "Creating wallet" : "Updating wallet data",
|
|
||||||
isDesktop: Util.isDesktop,
|
|
||||||
onException: (e) => ex = e,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ex != null) {
|
|
||||||
throw ex!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
ref.read(pFrostResharingData).reset();
|
|
||||||
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(
|
|
||||||
_popUntilPath,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"$e\n$s",
|
|
||||||
level: LogLevel.Fatal,
|
|
||||||
);
|
|
||||||
if (mounted) {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => StackOkDialog(
|
|
||||||
title: "Error",
|
|
||||||
message: e.toString(),
|
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
_buttonLock = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get _popUntilPath => isNew
|
|
||||||
? Util.isDesktop
|
|
||||||
? DesktopHomeView.routeName
|
|
||||||
: HomeView.routeName
|
|
||||||
: Util.isDesktop
|
|
||||||
? DesktopWalletView.routeName
|
|
||||||
: WalletView.routeName;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
config = ref.read(pFrostResharingData).newWalletData!.multisigConfig;
|
|
||||||
serializedKeys =
|
|
||||||
ref.read(pFrostResharingData).newWalletData!.serializedKeys;
|
|
||||||
reshareId = ref.read(pFrostResharingData).newWalletData!.resharedId;
|
|
||||||
|
|
||||||
isNew = ref.read(pFrostResharingData).incompleteWallet != null &&
|
|
||||||
ref.read(pFrostResharingData).incompleteWallet!.walletId ==
|
|
||||||
widget.walletId;
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: _popUntilPath,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: Util.isDesktop,
|
|
||||||
builder: (child) => DesktopScaffold(
|
|
||||||
background: Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: DesktopAppBar(
|
|
||||||
isCompactHeight: false,
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: _popUntilPath,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
trailing: ExitToMyStackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: _popUntilPath,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox(
|
|
||||||
width: 480,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ConditionalParent(
|
|
||||||
condition: !Util.isDesktop,
|
|
||||||
builder: (child) => Background(
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: AppBarBackButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => FrostInterruptionDialog(
|
|
||||||
type: FrostInterruptionDialogType.resharing,
|
|
||||||
popUntilOnYesRouteName: _popUntilPath,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Ensure your reshare ID matches that of each other participant",
|
|
||||||
style: STextStyles.pageTitleH2(context),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "ID",
|
|
||||||
detail: reshareId,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: reshareId,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: reshareId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
const _Div(),
|
|
||||||
Text(
|
|
||||||
"Back up your keys and config",
|
|
||||||
style: STextStyles.pageTitleH2(context),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "Config",
|
|
||||||
detail: config,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: config,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: config,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const _Div(),
|
|
||||||
DetailItem(
|
|
||||||
title: "Keys",
|
|
||||||
detail: serializedKeys,
|
|
||||||
button: Util.isDesktop
|
|
||||||
? IconCopyButton(
|
|
||||||
data: serializedKeys,
|
|
||||||
)
|
|
||||||
: SimpleCopyButton(
|
|
||||||
data: serializedKeys,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
|
||||||
const _Div(),
|
|
||||||
PrimaryButton(
|
|
||||||
label: "Confirm",
|
|
||||||
onPressed: _onPressed,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Div extends StatelessWidget {
|
|
||||||
const _Div({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -35,12 +35,12 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
class WalletBackupView extends ConsumerWidget {
|
class WalletBackupView extends ConsumerWidget {
|
||||||
const WalletBackupView({
|
const WalletBackupView({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.mnemonic,
|
required this.mnemonic,
|
||||||
this.frostWalletData,
|
this.frostWalletData,
|
||||||
this.clipboardInterface = const ClipboardWrapper(),
|
this.clipboardInterface = const ClipboardWrapper(),
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
static const String routeName = "/walletBackup";
|
static const String routeName = "/walletBackup";
|
||||||
|
|
||||||
|
|
|
@ -53,13 +53,13 @@ import 'package:tuple/tuple.dart';
|
||||||
/// [eventBus] should only be set during testing
|
/// [eventBus] should only be set during testing
|
||||||
class WalletSettingsView extends ConsumerStatefulWidget {
|
class WalletSettingsView extends ConsumerStatefulWidget {
|
||||||
const WalletSettingsView({
|
const WalletSettingsView({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.coin,
|
required this.coin,
|
||||||
required this.initialSyncStatus,
|
required this.initialSyncStatus,
|
||||||
required this.initialNodeStatus,
|
required this.initialNodeStatus,
|
||||||
this.eventBus,
|
this.eventBus,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
static const String routeName = "/walletSettings";
|
static const String routeName = "/walletSettings";
|
||||||
|
|
||||||
|
@ -195,21 +195,6 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (coin == Coin.bitcoinFrost ||
|
|
||||||
coin == Coin.bitcoinFrostTestNet)
|
|
||||||
if (coin == Coin.bitcoinFrost ||
|
|
||||||
coin == Coin.bitcoinFrostTestNet)
|
|
||||||
SettingsListButton(
|
|
||||||
iconAssetName: Assets.svg.addressBook2,
|
|
||||||
iconSize: 16,
|
|
||||||
title: "FROST Multisig settings",
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
FrostMSWalletOptionsView.routeName,
|
|
||||||
arguments: walletId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SettingsListButton(
|
SettingsListButton(
|
||||||
iconAssetName: Assets.svg.addressBook,
|
iconAssetName: Assets.svg.addressBook,
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
|
@ -221,6 +206,22 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (coin.isFrost)
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
if (coin.isFrost)
|
||||||
|
SettingsListButton(
|
||||||
|
iconAssetName: Assets.svg.addressBook2,
|
||||||
|
iconSize: 16,
|
||||||
|
title: "FROST Multisig settings",
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
FrostMSWalletOptionsView.routeName,
|
||||||
|
arguments: walletId,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
|
@ -266,31 +267,27 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
||||||
})? prevGen,
|
})? prevGen,
|
||||||
})? frostWalletData;
|
})? frostWalletData;
|
||||||
if (wallet is BitcoinFrostWallet) {
|
if (wallet is BitcoinFrostWallet) {
|
||||||
List<Future<dynamic>> futures = [];
|
final futures = [
|
||||||
|
wallet.getSerializedKeys(),
|
||||||
futures.addAll(
|
wallet.getMultisigConfig(),
|
||||||
[
|
wallet.getSerializedKeysPrevGen(),
|
||||||
wallet.getSerializedKeys(),
|
wallet.getMultisigConfigPrevGen(),
|
||||||
wallet.getMultisigConfig(),
|
];
|
||||||
wallet.getSerializedKeysPrevGen(),
|
|
||||||
wallet.getMultisigConfigPrevGen(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final results =
|
final results =
|
||||||
await Future.wait(futures);
|
await Future.wait(futures);
|
||||||
|
|
||||||
if (results.length == 5) {
|
if (results.length == 4) {
|
||||||
frostWalletData = (
|
frostWalletData = (
|
||||||
myName: wallet.frostInfo.myName,
|
myName: wallet.frostInfo.myName,
|
||||||
config: results[1],
|
config: results[1]!,
|
||||||
keys: results[0],
|
keys: results[0]!,
|
||||||
prevGen: results[2] == null ||
|
prevGen: results[2] == null ||
|
||||||
results[3] == null
|
results[3] == null
|
||||||
? null
|
? null
|
||||||
: (
|
: (
|
||||||
config: results[3],
|
config: results[3]!,
|
||||||
keys: results[2],
|
keys: results[2]!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -300,7 +297,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
||||||
await wallet.getMnemonicAsWords();
|
await wallet.getMnemonicAsWords();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (context.mounted) {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
RouteGenerator.getRoute(
|
RouteGenerator.getRoute(
|
||||||
|
|
|
@ -83,7 +83,7 @@ class _ChangeRepresentativeViewState
|
||||||
whileFuture: changeFuture(_textController.text),
|
whileFuture: changeFuture(_textController.text),
|
||||||
context: context,
|
context: context,
|
||||||
message: "Updating representative...",
|
message: "Updating representative...",
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
onException: (ex) {
|
onException: (ex) {
|
||||||
String msg = ex.toString();
|
String msg = ex.toString();
|
||||||
while (msg.isNotEmpty && msg.startsWith("Exception:")) {
|
while (msg.isNotEmpty && msg.startsWith("Exception:")) {
|
||||||
|
|
|
@ -98,7 +98,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
|
||||||
final success = await showLoading<bool>(
|
final success = await showLoading<bool>(
|
||||||
whileFuture: _loadTokenWallet(context, ref),
|
whileFuture: _loadTokenWallet(context, ref),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: isDesktop,
|
rootNavigator: isDesktop,
|
||||||
message: "Loading ${widget.token.name}",
|
message: "Loading ${widget.token.name}",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart';
|
||||||
|
@ -78,12 +79,14 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||||
import 'package:stackwallet/widgets/small_tor_icon.dart';
|
import 'package:stackwallet/widgets/small_tor_icon.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/frost_sign_nav_icon.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/fusion_nav_icon.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/ordinals_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/ordinals_nav_icon.dart';
|
||||||
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart';
|
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart';
|
||||||
|
@ -356,7 +359,26 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onExchangePressed(BuildContext context) async {
|
Future<void> _onFrostSignPressed(BuildContext context) async {
|
||||||
|
final wallet = ref.read(pWallets).getWallet(walletId) as BitcoinFrostWallet;
|
||||||
|
ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
|
info: (
|
||||||
|
walletName: wallet.info.name,
|
||||||
|
frostCurrency: wallet.cryptoCurrency,
|
||||||
|
),
|
||||||
|
walletId: walletId,
|
||||||
|
stepRoutes: FrostRouteGenerator.signFrostTxStepRoutes,
|
||||||
|
parentNav: Navigator.of(context),
|
||||||
|
frostInterruptionDialogType:
|
||||||
|
FrostInterruptionDialogType.transactionCreation,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostStepScaffold.routeName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onExchangePressed(BuildContext context) async {
|
||||||
final Coin coin = ref.read(pWalletCoin(walletId));
|
final Coin coin = ref.read(pWalletCoin(walletId));
|
||||||
|
|
||||||
if (coin.isTestNet) {
|
if (coin.isTestNet) {
|
||||||
|
@ -401,7 +423,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onBuyPressed(BuildContext context) async {
|
Future<void> _onBuyPressed(BuildContext context) async {
|
||||||
final Coin coin = ref.read(pWalletCoin(walletId));
|
final Coin coin = ref.read(pWalletCoin(walletId));
|
||||||
|
|
||||||
if (coin.isTestNet) {
|
if (coin.isTestNet) {
|
||||||
|
@ -976,6 +998,12 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (ref.watch(pWalletCoin(walletId)).isFrost)
|
||||||
|
WalletNavigationBarItemData(
|
||||||
|
label: "Sign",
|
||||||
|
icon: const FrostSignNavIcon(),
|
||||||
|
onTap: () => _onFrostSignPressed(context),
|
||||||
|
),
|
||||||
WalletNavigationBarItemData(
|
WalletNavigationBarItemData(
|
||||||
label: "Send",
|
label: "Send",
|
||||||
icon: const SendNavIcon(),
|
icon: const SendNavIcon(),
|
||||||
|
@ -1007,13 +1035,15 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (Constants.enableExchange)
|
if (Constants.enableExchange &&
|
||||||
|
!ref.watch(pWalletCoin(walletId)).isFrost)
|
||||||
WalletNavigationBarItemData(
|
WalletNavigationBarItemData(
|
||||||
label: "Swap",
|
label: "Swap",
|
||||||
icon: const ExchangeNavIcon(),
|
icon: const ExchangeNavIcon(),
|
||||||
onTap: () => _onExchangePressed(context),
|
onTap: () => _onExchangePressed(context),
|
||||||
),
|
),
|
||||||
if (Constants.enableExchange)
|
if (Constants.enableExchange &&
|
||||||
|
!ref.watch(pWalletCoin(walletId)).isFrost)
|
||||||
WalletNavigationBarItemData(
|
WalletNavigationBarItemData(
|
||||||
label: "Buy",
|
label: "Buy",
|
||||||
icon: const BuyNavIcon(),
|
icon: const BuyNavIcon(),
|
||||||
|
|
|
@ -127,7 +127,7 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
whileFuture: loadFuture,
|
whileFuture: loadFuture,
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Opening ${wallet.info.name}',
|
message: 'Opening ${wallet.info.name}',
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
|
@ -95,7 +95,7 @@ class WalletListItem extends ConsumerWidget {
|
||||||
whileFuture: loadFuture,
|
whileFuture: loadFuture,
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Opening ${wallet.info.name}',
|
message: 'Opening ${wallet.info.name}',
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
unawaited(
|
unawaited(
|
||||||
|
|
|
@ -132,7 +132,7 @@ class _FusionDialogViewState extends ConsumerState<FusionDialogView> {
|
||||||
Future<void>.delayed(const Duration(seconds: 2)),
|
Future<void>.delayed(const Duration(seconds: 2)),
|
||||||
]),
|
]),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: true,
|
rootNavigator: true,
|
||||||
message: "Stopping fusion",
|
message: "Stopping fusion",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ class CoinWalletsTable extends ConsumerWidget {
|
||||||
whileFuture: loadFuture,
|
whileFuture: loadFuture,
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Opening ${wallet.info.name}',
|
message: 'Opening ${wallet.info.name}',
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart';
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart';
|
import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart';
|
||||||
|
@ -21,14 +21,15 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
import 'package:stackwallet/widgets/custom_tab_view.dart';
|
import 'package:stackwallet/widgets/custom_tab_view.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class MyWallet extends ConsumerStatefulWidget {
|
class MyWallet extends ConsumerStatefulWidget {
|
||||||
const MyWallet({
|
const MyWallet({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
this.contractAddress,
|
this.contractAddress,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final String? contractAddress;
|
final String? contractAddress;
|
||||||
|
@ -85,10 +86,28 @@ class _MyWalletState extends ConsumerState<MyWallet> {
|
||||||
width: 200,
|
width: 200,
|
||||||
buttonHeight: ButtonHeight.l,
|
buttonHeight: ButtonHeight.l,
|
||||||
label: "Import sign config",
|
label: "Import sign config",
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
Navigator.of(context).pushNamed(
|
final wallet = ref
|
||||||
FrostImportSignConfigView.routeName,
|
.read(pWallets)
|
||||||
arguments: widget.walletId,
|
.getWallet(widget.walletId)
|
||||||
|
as BitcoinFrostWallet;
|
||||||
|
ref.read(pFrostScaffoldArgs.state).state =
|
||||||
|
(
|
||||||
|
info: (
|
||||||
|
walletName: wallet.info.name,
|
||||||
|
frostCurrency: wallet.cryptoCurrency,
|
||||||
|
),
|
||||||
|
walletId: widget.walletId,
|
||||||
|
stepRoutes: FrostRouteGenerator
|
||||||
|
.signFrostTxStepRoutes,
|
||||||
|
parentNav: Navigator.of(context),
|
||||||
|
frostInterruptionDialogType:
|
||||||
|
FrostInterruptionDialogType
|
||||||
|
.transactionCreation,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostStepScaffold.routeName,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -92,8 +92,6 @@ class _UnlockWalletKeysDesktopState
|
||||||
keys: (await wallet.getSerializedKeys())!,
|
keys: (await wallet.getSerializedKeys())!,
|
||||||
config: (await wallet.getMultisigConfig())!,
|
config: (await wallet.getMultisigConfig())!,
|
||||||
);
|
);
|
||||||
print(1111111);
|
|
||||||
print(frostData);
|
|
||||||
} else {
|
} else {
|
||||||
throw Exception("FIXME ~= see todo in code");
|
throw Exception("FIXME ~= see todo in code");
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,7 @@ class _DesktopOrdinalDetailsViewState
|
||||||
final path = await showLoading<String>(
|
final path = await showLoading<String>(
|
||||||
whileFuture: _savePngToFile(),
|
whileFuture: _savePngToFile(),
|
||||||
context: context,
|
context: context,
|
||||||
isDesktop: true,
|
rootNavigator: true,
|
||||||
message: "Saving ordinal image",
|
message: "Saving ordinal image",
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
didError = true;
|
didError = true;
|
||||||
|
|
|
@ -210,7 +210,7 @@ class _DesktopOrdinals extends ConsumerState<DesktopOrdinalsView> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// show loading for a minimum of 2 seconds on refreshing
|
// show loading for a minimum of 2 seconds on refreshing
|
||||||
await showLoading(
|
await showLoading(
|
||||||
isDesktop: true,
|
rootNavigator: true,
|
||||||
whileFuture: Future.wait<void>([
|
whileFuture: Future.wait<void>([
|
||||||
Future.delayed(const Duration(seconds: 2)),
|
Future.delayed(const Duration(seconds: 2)),
|
||||||
(ref.read(pWallets).getWallet(widget.walletId)
|
(ref.read(pWallets).getWallet(widget.walletId)
|
||||||
|
|
|
@ -60,13 +60,14 @@ class _ResharingData {
|
||||||
IncompleteFrostWallet? incompleteWallet;
|
IncompleteFrostWallet? incompleteWallet;
|
||||||
|
|
||||||
// resharer encoded config string
|
// resharer encoded config string
|
||||||
String? resharerConfig;
|
String? resharerRConfig;
|
||||||
|
|
||||||
({
|
({
|
||||||
int newThreshold,
|
int newThreshold,
|
||||||
List<int> resharers,
|
Map<String, int> resharers,
|
||||||
List<String> newParticipants,
|
List<String> newParticipants,
|
||||||
})? get configData => resharerConfig != null
|
})? get configData => resharerRConfig != null
|
||||||
? Frost.extractResharerConfigData(resharerConfig: resharerConfig!)
|
? Frost.extractResharerConfigData(rConfig: resharerRConfig!)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// resharer start string (for sharing) and machine
|
// resharer start string (for sharing) and machine
|
||||||
|
@ -93,7 +94,7 @@ class _ResharingData {
|
||||||
|
|
||||||
// reset/clear all data
|
// reset/clear all data
|
||||||
void reset() {
|
void reset() {
|
||||||
resharerConfig = null;
|
resharerRConfig = null;
|
||||||
startResharerData = null;
|
startResharerData = null;
|
||||||
startResharedData = null;
|
startResharedData = null;
|
||||||
resharerComplete = null;
|
resharerComplete = null;
|
||||||
|
|
|
@ -26,12 +26,8 @@ import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_tok
|
||||||
import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/confirm_new_frost_ms_wallet_creation_view.dart';
|
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_shares_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/share_new_multisig_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_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_options/new_wallet_options_view.dart';
|
||||||
|
@ -83,9 +79,6 @@ import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.d
|
||||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||||
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_attempt_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_create_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_import_sign_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart';
|
import 'package:stackwallet/pages/send_view/frost_ms/frost_send_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/send_view.dart';
|
import 'package:stackwallet/pages/send_view/send_view.dart';
|
||||||
import 'package:stackwallet/pages/send_view/token_send_view.dart';
|
import 'package:stackwallet/pages/send_view/token_send_view.dart';
|
||||||
|
@ -126,17 +119,8 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_pr
|
||||||
import 'package:stackwallet/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart';
|
import 'package:stackwallet/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_ms_options_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/frost_participants_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/finish_resharing_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/complete_reshare_config_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/begin_reshare_config_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/initiate_resharing/initiate_resharing_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/complete_reshare_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1a/display_reshare_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_1b/import_reshare_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/begin_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/involved/step_2/continue_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_continue_sharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/new/new_start_resharing_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/frost_ms/resharing/verify_updated_wallet_view.dart';
|
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
|
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
|
||||||
|
@ -205,9 +189,11 @@ import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||||
import 'package:stackwallet/widgets/choose_coin_view.dart';
|
import 'package:stackwallet/widgets/choose_coin_view.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_scaffold.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -450,13 +436,13 @@ class RouteGenerator {
|
||||||
case CreateNewFrostMsWalletView.routeName:
|
case CreateNewFrostMsWalletView.routeName:
|
||||||
if (args is ({
|
if (args is ({
|
||||||
String walletName,
|
String walletName,
|
||||||
Coin coin,
|
FrostCurrency frostCurrency,
|
||||||
})) {
|
})) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => CreateNewFrostMsWalletView(
|
builder: (_) => CreateNewFrostMsWalletView(
|
||||||
walletName: args.walletName,
|
walletName: args.walletName,
|
||||||
coin: args.coin,
|
frostCurrency: args.frostCurrency,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
@ -468,13 +454,13 @@ class RouteGenerator {
|
||||||
case RestoreFrostMsWalletView.routeName:
|
case RestoreFrostMsWalletView.routeName:
|
||||||
if (args is ({
|
if (args is ({
|
||||||
String walletName,
|
String walletName,
|
||||||
Coin coin,
|
FrostCurrency frostCurrency,
|
||||||
})) {
|
})) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => RestoreFrostMsWalletView(
|
builder: (_) => RestoreFrostMsWalletView(
|
||||||
walletName: args.walletName,
|
walletName: args.walletName,
|
||||||
coin: args.coin,
|
frostCurrency: args.frostCurrency,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
@ -483,16 +469,16 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case ShareNewMultisigConfigView.routeName:
|
case SelectNewFrostImportTypeView.routeName:
|
||||||
if (args is ({
|
if (args is ({
|
||||||
String walletName,
|
String walletName,
|
||||||
Coin coin,
|
FrostCurrency frostCurrency,
|
||||||
})) {
|
})) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => ShareNewMultisigConfigView(
|
builder: (_) => SelectNewFrostImportTypeView(
|
||||||
walletName: args.walletName,
|
walletName: args.walletName,
|
||||||
coin: args.coin,
|
frostCurrency: args.frostCurrency,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
|
@ -501,123 +487,14 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case ImportNewFrostMsWalletView.routeName:
|
case FrostStepScaffold.routeName:
|
||||||
if (args is ({
|
return getRoute(
|
||||||
String walletName,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
Coin coin,
|
builder: (_) => const FrostStepScaffold(),
|
||||||
})) {
|
settings: RouteSettings(
|
||||||
return getRoute(
|
name: settings.name,
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
),
|
||||||
builder: (_) => ImportNewFrostMsWalletView(
|
);
|
||||||
walletName: args.walletName,
|
|
||||||
coin: args.coin,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case NewImportResharerConfigView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
Coin coin,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => NewImportResharerConfigView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
coin: args.coin,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case NewStartResharingView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => NewStartResharingView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case NewContinueSharingView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => NewContinueSharingView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostShareCommitmentsView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
Coin coin,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostShareCommitmentsView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
coin: args.coin,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostShareSharesView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
Coin coin,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostShareSharesView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
coin: args.coin,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case ConfirmNewFrostMSWalletCreationView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
Coin coin,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => ConfirmNewFrostMSWalletCreationView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
coin: args.coin,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostMSWalletOptionsView.routeName:
|
case FrostMSWalletOptionsView.routeName:
|
||||||
if (args is String) {
|
if (args is String) {
|
||||||
|
@ -647,25 +524,11 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case ImportReshareConfigView.routeName:
|
case InitiateResharingView.routeName:
|
||||||
if (args is String) {
|
if (args is String) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => ImportReshareConfigView(
|
builder: (_) => InitiateResharingView(
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case BeginReshareConfigView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => BeginReshareConfigView(
|
|
||||||
walletId: args,
|
walletId: args,
|
||||||
),
|
),
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
|
@ -676,7 +539,7 @@ class RouteGenerator {
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case CompleteReshareConfigView.routeName:
|
case CompleteReshareConfigView.routeName:
|
||||||
if (args is ({String walletId, List<int> resharers})) {
|
if (args is ({String walletId, Map<String, int> resharers})) {
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => CompleteReshareConfigView(
|
builder: (_) => CompleteReshareConfigView(
|
||||||
|
@ -690,76 +553,6 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case DisplayReshareConfigView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => DisplayReshareConfigView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case BeginResharingView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => BeginResharingView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case ContinueResharingView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => ContinueResharingView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FinishResharingView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FinishResharingView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case VerifyUpdatedWalletView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => VerifyUpdatedWalletView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostSendView.routeName:
|
case FrostSendView.routeName:
|
||||||
if (args is ({
|
if (args is ({
|
||||||
String walletId,
|
String walletId,
|
||||||
|
@ -778,48 +571,6 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case FrostImportSignConfigView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostImportSignConfigView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostCreateSignConfigView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostCreateSignConfigView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostAttemptSignConfigView.routeName:
|
|
||||||
if (args is String) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostAttemptSignConfigView(
|
|
||||||
walletId: args,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
// case MonkeyLoadedView.routeName:
|
// case MonkeyLoadedView.routeName:
|
||||||
// if (args is Tuple2<String, ChangeNotifierProvider<Manager>>) {
|
// if (args is Tuple2<String, ChangeNotifierProvider<Manager>>) {
|
||||||
// return getRoute(
|
// return getRoute(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
@ -551,11 +552,14 @@ abstract class Frost {
|
||||||
|
|
||||||
static ({
|
static ({
|
||||||
int newThreshold,
|
int newThreshold,
|
||||||
List<int> resharers,
|
Map<String, int> resharers,
|
||||||
List<String> newParticipants,
|
List<String> newParticipants,
|
||||||
}) extractResharerConfigData({
|
}) extractResharerConfigData({
|
||||||
required String resharerConfig,
|
required String rConfig,
|
||||||
}) {
|
}) {
|
||||||
|
final decoded = _decodeRConfigWithResharers(rConfig);
|
||||||
|
final resharerConfig = decoded.config;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final newThreshold = resharerNewThreshold(
|
final newThreshold = resharerNewThreshold(
|
||||||
resharerConfigPointer: decodedResharerConfig(
|
resharerConfigPointer: decodedResharerConfig(
|
||||||
|
@ -597,9 +601,17 @@ abstract class Frost {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Map<String, int> resharersMap = {};
|
||||||
|
|
||||||
|
for (final resharer in resharers) {
|
||||||
|
resharersMap[decoded.resharers.entries
|
||||||
|
.firstWhere((e) => e.value == resharer)
|
||||||
|
.key] = resharer;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
newThreshold: newThreshold,
|
newThreshold: newThreshold,
|
||||||
resharers: resharers,
|
resharers: resharersMap,
|
||||||
newParticipants: newParticipants,
|
newParticipants: newParticipants,
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -610,4 +622,29 @@ abstract class Frost {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String encodeRConfig(
|
||||||
|
String config,
|
||||||
|
Map<String, int> resharers,
|
||||||
|
) {
|
||||||
|
return base64Encode("$config@${jsonEncode(resharers)}".toUint8ListFromUtf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String decodeRConfig(
|
||||||
|
String rConfig,
|
||||||
|
) {
|
||||||
|
return base64Decode(rConfig).toUtf8String.split("@").first;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ({Map<String, int> resharers, String config})
|
||||||
|
_decodeRConfigWithResharers(
|
||||||
|
String rConfig,
|
||||||
|
) {
|
||||||
|
final parts = base64Decode(rConfig).toUtf8String.split("@");
|
||||||
|
|
||||||
|
final config = parts[0];
|
||||||
|
final resharers = Map<String, int>.from(jsonDecode(parts[1]) as Map);
|
||||||
|
|
||||||
|
return (resharers: resharers, config: config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ class _SVG {
|
||||||
String get chevronDown => "assets/svg/chevron-down.svg";
|
String get chevronDown => "assets/svg/chevron-down.svg";
|
||||||
String get chevronUp => "assets/svg/chevron-up.svg";
|
String get chevronUp => "assets/svg/chevron-up.svg";
|
||||||
String get swap => "assets/svg/swap.svg";
|
String get swap => "assets/svg/swap.svg";
|
||||||
|
String get swap2 => "assets/svg/swap2.svg";
|
||||||
String get downloadFolder => "assets/svg/folder-down.svg";
|
String get downloadFolder => "assets/svg/folder-down.svg";
|
||||||
String get lock => "assets/svg/lock-keyhole.svg";
|
String get lock => "assets/svg/lock-keyhole.svg";
|
||||||
String get lockOpen => "assets/svg/lock-open.svg";
|
String get lockOpen => "assets/svg/lock-open.svg";
|
||||||
|
|
|
@ -69,7 +69,6 @@ class Prefs extends ChangeNotifier {
|
||||||
_useTor = await _getUseTor();
|
_useTor = await _getUseTor();
|
||||||
_fusionServerInfo = await _getFusionServerInfo();
|
_fusionServerInfo = await _getFusionServerInfo();
|
||||||
_solanaEnabled = await _getSolanaEnabled();
|
_solanaEnabled = await _getSolanaEnabled();
|
||||||
_frostEnabled = await _getFrostEnabled();
|
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
@ -1031,25 +1030,4 @@ class Prefs extends ChangeNotifier {
|
||||||
boxName: DB.boxNamePrefs, key: "solanaEnabled") as bool? ??
|
boxName: DB.boxNamePrefs, key: "solanaEnabled") as bool? ??
|
||||||
false;
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FROST multisig
|
|
||||||
|
|
||||||
bool _frostEnabled = false;
|
|
||||||
|
|
||||||
bool get frostEnabled => _frostEnabled;
|
|
||||||
|
|
||||||
set frostEnabled(bool frostEnabled) {
|
|
||||||
if (_frostEnabled != frostEnabled) {
|
|
||||||
DB.instance.put<dynamic>(
|
|
||||||
boxName: DB.boxNamePrefs, key: "frostEnabled", value: frostEnabled);
|
|
||||||
_frostEnabled = frostEnabled;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _getFrostEnabled() async {
|
|
||||||
return await DB.instance.get<dynamic>(
|
|
||||||
boxName: DB.boxNamePrefs, key: "frostEnabled") as bool? ??
|
|
||||||
false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ Future<T?> showLoading<T>({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String message,
|
required String message,
|
||||||
String? subMessage,
|
String? subMessage,
|
||||||
bool isDesktop = false,
|
bool rootNavigator = false,
|
||||||
bool opaqueBG = false,
|
bool opaqueBG = false,
|
||||||
void Function(Exception)? onException,
|
void Function(Exception)? onException,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -59,7 +59,7 @@ Future<T?> showLoading<T>({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
Navigator.of(context, rootNavigator: rootNavigator).pop();
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
onException?.call(ex);
|
onException?.call(ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -383,6 +383,28 @@ class STextStyles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TextStyle w400_16(BuildContext context) {
|
||||||
|
switch (_theme(context).themeId) {
|
||||||
|
default:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextStyle w400_14(BuildContext context) {
|
||||||
|
switch (_theme(context).themeId) {
|
||||||
|
default:
|
||||||
|
return GoogleFonts.inter(
|
||||||
|
color: _theme(context).textDark,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static TextStyle w600_20(BuildContext context) {
|
static TextStyle w600_20(BuildContext context) {
|
||||||
switch (_theme(context).themeId) {
|
switch (_theme(context).themeId) {
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -6,15 +6,15 @@ import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||||
|
|
||||||
class BitcoinFrost extends FrostCurrency {
|
class BitcoinFrost extends FrostCurrency {
|
||||||
BitcoinFrost(super.network) {
|
BitcoinFrost(super.network) {
|
||||||
switch (network) {
|
switch (network) {
|
||||||
case CryptoCurrencyNetwork.main:
|
case CryptoCurrencyNetwork.main:
|
||||||
coin = Coin.bitcoin;
|
coin = Coin.bitcoinFrost;
|
||||||
case CryptoCurrencyNetwork.test:
|
case CryptoCurrencyNetwork.test:
|
||||||
coin = Coin.bitcoinTestNet;
|
coin = Coin.bitcoinFrostTestNet;
|
||||||
default:
|
default:
|
||||||
throw Exception("Unsupported network: $network");
|
throw Exception("Unsupported network: $network");
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,9 @@ class IncompleteFrostWallet {
|
||||||
threshold: -1,
|
threshold: -1,
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainDB.isar.frostWalletInfo.put(frostInfo);
|
await mainDB.isar.writeTxn(() async {
|
||||||
|
await mainDB.isar.frostWalletInfo.put(frostInfo);
|
||||||
|
});
|
||||||
|
|
||||||
return wallet as BitcoinFrostWallet;
|
return wallet as BitcoinFrostWallet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,9 @@ class TxData {
|
||||||
|
|
||||||
final String? changeAddress;
|
final String? changeAddress;
|
||||||
|
|
||||||
|
// frost specific
|
||||||
final String? frostMSConfig;
|
final String? frostMSConfig;
|
||||||
|
final List<String>? frostSigners;
|
||||||
|
|
||||||
// paynym specific
|
// paynym specific
|
||||||
final PaynymAccountLite? paynymAccountLite;
|
final PaynymAccountLite? paynymAccountLite;
|
||||||
|
@ -91,6 +93,7 @@ class TxData {
|
||||||
this.usedUTXOs,
|
this.usedUTXOs,
|
||||||
this.changeAddress,
|
this.changeAddress,
|
||||||
this.frostMSConfig,
|
this.frostMSConfig,
|
||||||
|
this.frostSigners,
|
||||||
this.paynymAccountLite,
|
this.paynymAccountLite,
|
||||||
this.web3dartTransaction,
|
this.web3dartTransaction,
|
||||||
this.nonce,
|
this.nonce,
|
||||||
|
@ -166,6 +169,7 @@ class TxData {
|
||||||
})>?
|
})>?
|
||||||
recipients,
|
recipients,
|
||||||
String? frostMSConfig,
|
String? frostMSConfig,
|
||||||
|
List<String>? frostSigners,
|
||||||
String? changeAddress,
|
String? changeAddress,
|
||||||
PaynymAccountLite? paynymAccountLite,
|
PaynymAccountLite? paynymAccountLite,
|
||||||
web3dart.Transaction? web3dartTransaction,
|
web3dart.Transaction? web3dartTransaction,
|
||||||
|
@ -209,6 +213,7 @@ class TxData {
|
||||||
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
||||||
recipients: recipients ?? this.recipients,
|
recipients: recipients ?? this.recipients,
|
||||||
frostMSConfig: frostMSConfig ?? this.frostMSConfig,
|
frostMSConfig: frostMSConfig ?? this.frostMSConfig,
|
||||||
|
frostSigners: frostSigners ?? this.frostSigners,
|
||||||
changeAddress: changeAddress ?? this.changeAddress,
|
changeAddress: changeAddress ?? this.changeAddress,
|
||||||
paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite,
|
paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite,
|
||||||
web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction,
|
web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction,
|
||||||
|
@ -249,7 +254,7 @@ class TxData {
|
||||||
'recipients: $recipients, '
|
'recipients: $recipients, '
|
||||||
'utxos: $utxos, '
|
'utxos: $utxos, '
|
||||||
'usedUTXOs: $usedUTXOs, '
|
'usedUTXOs: $usedUTXOs, '
|
||||||
'frostMSConfig: $frostMSConfig, '
|
'frostSigners: $frostSigners, '
|
||||||
'changeAddress: $changeAddress, '
|
'changeAddress: $changeAddress, '
|
||||||
'paynymAccountLite: $paynymAccountLite, '
|
'paynymAccountLite: $paynymAccountLite, '
|
||||||
'web3dartTransaction: $web3dartTransaction, '
|
'web3dartTransaction: $web3dartTransaction, '
|
||||||
|
|
|
@ -23,7 +23,7 @@ import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
|
import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin_frost.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/private_key_currency.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/frost_wallet_info.dart';
|
||||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||||
|
@ -41,7 +41,6 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
|
||||||
late CachedElectrumXClient electrumXCachedClient;
|
late CachedElectrumXClient electrumXCachedClient;
|
||||||
|
|
||||||
Future<void> initializeNewFrost({
|
Future<void> initializeNewFrost({
|
||||||
required String mnemonic,
|
|
||||||
required String multisigConfig,
|
required String multisigConfig,
|
||||||
required String recoveryString,
|
required String recoveryString,
|
||||||
required String serializedKeys,
|
required String serializedKeys,
|
||||||
|
@ -70,14 +69,6 @@ class BitcoinFrostWallet<T extends FrostCurrency> extends Wallet<T> {
|
||||||
threshold: threshold,
|
threshold: threshold,
|
||||||
);
|
);
|
||||||
|
|
||||||
await secureStorageInterface.write(
|
|
||||||
key: Wallet.mnemonicKey(walletId: info.walletId),
|
|
||||||
value: mnemonic,
|
|
||||||
);
|
|
||||||
await secureStorageInterface.write(
|
|
||||||
key: Wallet.mnemonicPassphraseKey(walletId: info.walletId),
|
|
||||||
value: "",
|
|
||||||
);
|
|
||||||
await _saveSerializedKeys(serializedKeys);
|
await _saveSerializedKeys(serializedKeys);
|
||||||
await _saveRecoveryString(recoveryString);
|
await _saveRecoveryString(recoveryString);
|
||||||
await _saveMultisigId(multisigId);
|
await _saveMultisigId(multisigId);
|
||||||
|
|
|
@ -50,9 +50,9 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
|
|
||||||
double highestPercent = 0;
|
double highestPercent = 0;
|
||||||
Future<double> get getSyncPercent async {
|
Future<double> get getSyncPercent async {
|
||||||
int lastScannedBlock = info.epicData?.lastScannedBlock ?? 0;
|
final int lastScannedBlock = info.epicData?.lastScannedBlock ?? 0;
|
||||||
final _chainHeight = await chainHeight;
|
final _chainHeight = await chainHeight;
|
||||||
double restorePercent = lastScannedBlock / _chainHeight;
|
final double restorePercent = lastScannedBlock / _chainHeight;
|
||||||
GlobalEventBus.instance
|
GlobalEventBus.instance
|
||||||
.fire(RefreshPercentChangedEvent(highestPercent, walletId));
|
.fire(RefreshPercentChangedEvent(highestPercent, walletId));
|
||||||
if (restorePercent > highestPercent) {
|
if (restorePercent > highestPercent) {
|
||||||
|
@ -67,7 +67,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateEpicboxConfig(String host, int port) async {
|
Future<void> updateEpicboxConfig(String host, int port) async {
|
||||||
String stringConfig = jsonEncode({
|
final String stringConfig = jsonEncode({
|
||||||
"epicbox_domain": host,
|
"epicbox_domain": host,
|
||||||
"epicbox_port": port,
|
"epicbox_port": port,
|
||||||
"epicbox_protocol_unsecure": false,
|
"epicbox_protocol_unsecure": false,
|
||||||
|
@ -103,7 +103,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<EpicBoxConfigModel> getEpicBoxConfig() async {
|
Future<EpicBoxConfigModel> getEpicBoxConfig() async {
|
||||||
EpicBoxConfigModel? _epicBoxConfig = EpicBoxConfigModel.fromServer(
|
final EpicBoxConfigModel _epicBoxConfig = EpicBoxConfigModel.fromServer(
|
||||||
DefaultEpicBoxes.defaultEpicBoxServer,
|
DefaultEpicBoxes.defaultEpicBoxServer,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -156,12 +156,12 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
config["api_listen_port"] = port;
|
config["api_listen_port"] = port;
|
||||||
config["api_listen_interface"] =
|
config["api_listen_interface"] =
|
||||||
nodeApiAddress.replaceFirst(uri.scheme, "");
|
nodeApiAddress.replaceFirst(uri.scheme, "");
|
||||||
String stringConfig = jsonEncode(config);
|
final String stringConfig = jsonEncode(config);
|
||||||
return stringConfig;
|
return stringConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _currentWalletDirPath() async {
|
Future<String> _currentWalletDirPath() async {
|
||||||
Directory appDir = await StackFileSystem.applicationRootDirectory();
|
final Directory appDir = await StackFileSystem.applicationRootDirectory();
|
||||||
|
|
||||||
final path = "${appDir.path}/epiccash";
|
final path = "${appDir.path}/epiccash";
|
||||||
final String name = walletId.trim();
|
final String name = walletId.trim();
|
||||||
|
@ -176,7 +176,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
try {
|
try {
|
||||||
final available = info.cachedBalance.spendable.raw.toInt();
|
final available = info.cachedBalance.spendable.raw.toInt();
|
||||||
|
|
||||||
var transactionFees = await epiccash.LibEpiccash.getTransactionFees(
|
final transactionFees = await epiccash.LibEpiccash.getTransactionFees(
|
||||||
wallet: wallet!,
|
wallet: wallet!,
|
||||||
amount: satoshiAmount,
|
amount: satoshiAmount,
|
||||||
minimumConfirmations: cryptoCurrency.minConfirms,
|
minimumConfirmations: cryptoCurrency.minConfirms,
|
||||||
|
@ -303,21 +303,20 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
Future<Address> _generateAndStoreReceivingAddressForIndex(
|
Future<Address> _generateAndStoreReceivingAddressForIndex(
|
||||||
int index,
|
int index,
|
||||||
) async {
|
) async {
|
||||||
|
|
||||||
Address? address = await getCurrentReceivingAddress();
|
Address? address = await getCurrentReceivingAddress();
|
||||||
EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
|
|
||||||
|
|
||||||
if (address != null) {
|
if (address != null) {
|
||||||
final splitted = address.value.split('@');
|
final splitted = address.value.split('@');
|
||||||
//Check if the address is the same as the current epicbox domain
|
//Check if the address is the same as the current epicbox domain
|
||||||
//Since we're only using one epicbpox now this doesn't apply but will be
|
//Since we're only using one epicbpox now this doesn't apply but will be
|
||||||
// useful in the future
|
// useful in the future
|
||||||
final encodedConfig = jsonEncode(epicboxConfig);
|
final epicboxConfig = await getEpicBoxConfig();
|
||||||
if (splitted[1] != epicboxConfig.host) {
|
if (splitted[1] != epicboxConfig.host) {
|
||||||
//Update the address
|
//Update the address
|
||||||
address = await thisWalletAddress(index, epicboxConfig);
|
address = await thisWalletAddress(index, epicboxConfig);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
final epicboxConfig = await getEpicBoxConfig();
|
||||||
address = await thisWalletAddress(index, epicboxConfig);
|
address = await thisWalletAddress(index, epicboxConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,9 +330,10 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Address> thisWalletAddress(
|
Future<Address> thisWalletAddress(
|
||||||
int index, EpicBoxConfigModel epicboxConfig) async {
|
int index,
|
||||||
|
EpicBoxConfigModel epicboxConfig,
|
||||||
|
) async {
|
||||||
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
|
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
|
||||||
// EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
|
|
||||||
|
|
||||||
final walletAddress = await epiccash.LibEpiccash.getAddressInfo(
|
final walletAddress = await epiccash.LibEpiccash.getAddressInfo(
|
||||||
wallet: wallet!,
|
wallet: wallet!,
|
||||||
|
@ -384,7 +384,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
level: LogLevel.Info,
|
level: LogLevel.Info,
|
||||||
);
|
);
|
||||||
|
|
||||||
int nextScannedBlock = await epiccash.LibEpiccash.scanOutputs(
|
final int nextScannedBlock = await epiccash.LibEpiccash.scanOutputs(
|
||||||
wallet: wallet!,
|
wallet: wallet!,
|
||||||
startHeight: lastScannedBlock,
|
startHeight: lastScannedBlock,
|
||||||
numberOfBlocks: scanChunkSize,
|
numberOfBlocks: scanChunkSize,
|
||||||
|
@ -424,7 +424,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
Future<void> _listenToEpicbox() async {
|
Future<void> _listenToEpicbox() async {
|
||||||
Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info);
|
Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info);
|
||||||
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
|
final wallet = await secureStorageInterface.read(key: '${walletId}_wallet');
|
||||||
EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
|
final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig();
|
||||||
epiccash.LibEpiccash.startEpicboxListener(
|
epiccash.LibEpiccash.startEpicboxListener(
|
||||||
wallet: wallet!,
|
wallet: wallet!,
|
||||||
epicboxConfig: epicboxConfig.toString(),
|
epicboxConfig: epicboxConfig.toString(),
|
||||||
|
@ -438,7 +438,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
);
|
);
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
final walletDir = await _currentWalletDirPath();
|
final walletDir = await _currentWalletDirPath();
|
||||||
var editConfig = jsonDecode(config as String);
|
final editConfig = jsonDecode(config as String);
|
||||||
|
|
||||||
editConfig["wallet_dir"] = walletDir;
|
editConfig["wallet_dir"] = walletDir;
|
||||||
config = jsonEncode(editConfig);
|
config = jsonEncode(editConfig);
|
||||||
|
@ -448,14 +448,11 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
|
|
||||||
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
|
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
|
||||||
int _calculateRestoreHeightFrom({required DateTime date}) {
|
int _calculateRestoreHeightFrom({required DateTime date}) {
|
||||||
int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000;
|
final int secondsSinceEpoch = date.millisecondsSinceEpoch ~/ 1000;
|
||||||
const int epicCashFirstBlock = 1565370278;
|
const int epicCashFirstBlock = 1565370278;
|
||||||
const double overestimateSecondsPerBlock = 61;
|
const double overestimateSecondsPerBlock = 61;
|
||||||
int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
||||||
int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
|
final int approximateHeight = chosenSeconds ~/ overestimateSecondsPerBlock;
|
||||||
//todo: check if print needed
|
|
||||||
// debugPrint(
|
|
||||||
// "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
|
|
||||||
int height = approximateHeight;
|
int height = approximateHeight;
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = 0;
|
height = 0;
|
||||||
|
@ -503,7 +500,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
await secureStorageInterface.write(
|
await secureStorageInterface.write(
|
||||||
key: '${walletId}_epicboxConfig', value: epicboxConfig.toString());
|
key: '${walletId}_epicboxConfig', value: epicboxConfig.toString());
|
||||||
|
|
||||||
String name = walletId;
|
final String name = walletId;
|
||||||
|
|
||||||
await epiccash.LibEpiccash.initializeNewWallet(
|
await epiccash.LibEpiccash.initializeNewWallet(
|
||||||
config: stringConfig,
|
config: stringConfig,
|
||||||
|
@ -587,7 +584,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
|
|
||||||
if (!receiverAddress.startsWith("http://") ||
|
if (!receiverAddress.startsWith("http://") ||
|
||||||
!receiverAddress.startsWith("https://")) {
|
!receiverAddress.startsWith("https://")) {
|
||||||
bool isEpicboxConnected = await _testEpicboxServer(
|
final bool isEpicboxConnected = await _testEpicboxServer(
|
||||||
epicboxConfig,
|
epicboxConfig,
|
||||||
);
|
);
|
||||||
if (!isEpicboxConnected) {
|
if (!isEpicboxConnected) {
|
||||||
|
@ -969,7 +966,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
],
|
],
|
||||||
walletOwns: true,
|
walletOwns: true,
|
||||||
);
|
);
|
||||||
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptSigHex: null,
|
scriptSigHex: null,
|
||||||
scriptSigAsm: null,
|
scriptSigAsm: null,
|
||||||
sequence: null,
|
sequence: null,
|
||||||
|
@ -1106,7 +1103,7 @@ class EpiccashWallet extends Bip39Wallet {
|
||||||
@override
|
@override
|
||||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||||
// setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function?????
|
// setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function?????
|
||||||
int currentFee = await _nativeFee(
|
final int currentFee = await _nativeFee(
|
||||||
amount.raw.toInt(),
|
amount.raw.toInt(),
|
||||||
ifErrorEstimateFee: true,
|
ifErrorEstimateFee: true,
|
||||||
);
|
);
|
||||||
|
@ -1151,13 +1148,13 @@ Future<String> deleteEpicWallet({
|
||||||
final wallet = await secureStore.read(key: '${walletId}_wallet');
|
final wallet = await secureStore.read(key: '${walletId}_wallet');
|
||||||
String? config = await secureStore.read(key: '${walletId}_config');
|
String? config = await secureStore.read(key: '${walletId}_config');
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
Directory appDir = await StackFileSystem.applicationRootDirectory();
|
final Directory appDir = await StackFileSystem.applicationRootDirectory();
|
||||||
|
|
||||||
final path = "${appDir.path}/epiccash";
|
final path = "${appDir.path}/epiccash";
|
||||||
final String name = walletId.trim();
|
final String name = walletId.trim();
|
||||||
final walletDir = '$path/$name';
|
final walletDir = '$path/$name';
|
||||||
|
|
||||||
var editConfig = jsonDecode(config as String);
|
final editConfig = jsonDecode(config as String);
|
||||||
|
|
||||||
editConfig["wallet_dir"] = walletDir;
|
editConfig["wallet_dir"] = walletDir;
|
||||||
config = jsonEncode(editConfig);
|
config = jsonEncode(editConfig);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:socks5_proxy/socks.dart';
|
import 'package:socks5_proxy/socks.dart';
|
||||||
import 'package:stackwallet/models/balance.dart';
|
import 'package:stackwallet/models/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||||
|
@ -11,6 +12,9 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/events/global/tor_status_changed_event.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
import 'package:stackwallet/services/tor_service.dart';
|
import 'package:stackwallet/services/tor_service.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||||
|
@ -23,11 +27,47 @@ import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
|
||||||
import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart' as stellar;
|
import 'package:stellar_flutter_sdk/stellar_flutter_sdk.dart' as stellar;
|
||||||
|
|
||||||
class StellarWallet extends Bip39Wallet<Stellar> {
|
class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
StellarWallet(CryptoCurrencyNetwork network) : super(Stellar(network));
|
StellarWallet(CryptoCurrencyNetwork network) : super(Stellar(network)) {
|
||||||
|
final bus = GlobalEventBus.instance;
|
||||||
|
|
||||||
stellar.StellarSDK get stellarSdk {
|
// Listen for tor status changes.
|
||||||
if (_stellarSdk == null) {
|
_torStatusListener = bus.on<TorConnectionStatusChangedEvent>().listen(
|
||||||
_updateSdk();
|
(event) async {
|
||||||
|
switch (event.newStatus) {
|
||||||
|
case TorConnectionStatus.connecting:
|
||||||
|
if (!_torConnectingLock.isLocked) {
|
||||||
|
await _torConnectingLock.acquire();
|
||||||
|
}
|
||||||
|
_requireMutex = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TorConnectionStatus.connected:
|
||||||
|
case TorConnectionStatus.disconnected:
|
||||||
|
if (_torConnectingLock.isLocked) {
|
||||||
|
_torConnectingLock.release();
|
||||||
|
}
|
||||||
|
_requireMutex = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Listen for tor preference changes.
|
||||||
|
_torPreferenceListener = bus.on<TorPreferenceChangedEvent>().listen(
|
||||||
|
(event) async {
|
||||||
|
_stellarSdk?.httpClient.close();
|
||||||
|
_stellarSdk = null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<stellar.StellarSDK> get stellarSdk async {
|
||||||
|
if (_requireMutex) {
|
||||||
|
await _torConnectingLock.protect(() async {
|
||||||
|
_stellarSdk ??= _getFreshSdk();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_stellarSdk ??= _getFreshSdk();
|
||||||
}
|
}
|
||||||
return _stellarSdk!;
|
return _stellarSdk!;
|
||||||
}
|
}
|
||||||
|
@ -44,39 +84,60 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== Private ====================================================
|
// ============== Private ====================================================
|
||||||
|
// add finalizer to cancel stream subscription when all references to an
|
||||||
|
// instance of this becomes inaccessible
|
||||||
|
final _ = Finalizer<StellarWallet>(
|
||||||
|
(p0) {
|
||||||
|
p0._torPreferenceListener?.cancel();
|
||||||
|
p0._torStatusListener?.cancel();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
StreamSubscription<TorConnectionStatusChangedEvent>? _torStatusListener;
|
||||||
|
StreamSubscription<TorPreferenceChangedEvent>? _torPreferenceListener;
|
||||||
|
|
||||||
|
final Mutex _torConnectingLock = Mutex();
|
||||||
|
bool _requireMutex = false;
|
||||||
|
|
||||||
stellar.StellarSDK? _stellarSdk;
|
stellar.StellarSDK? _stellarSdk;
|
||||||
HttpClient? _httpClient;
|
|
||||||
|
|
||||||
Future<int> _getBaseFee() async {
|
Future<int> _getBaseFee() async {
|
||||||
final fees = await stellarSdk.feeStats.execute();
|
final fees = await (await stellarSdk).feeStats.execute();
|
||||||
return int.parse(fees.lastLedgerBaseFee);
|
return int.parse(fees.lastLedgerBaseFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateSdk() {
|
stellar.StellarSDK _getFreshSdk() {
|
||||||
final currentNode = getCurrentNode();
|
final currentNode = getCurrentNode();
|
||||||
|
HttpClient? _httpClient;
|
||||||
|
|
||||||
// TODO [prio=med]: refactor out and call before requests in case Tor is enabled/disabled, listen to prefs change, or similar.
|
|
||||||
if (prefs.useTor) {
|
if (prefs.useTor) {
|
||||||
final ({InternetAddress host, int port}) proxyInfo =
|
final ({InternetAddress host, int port}) proxyInfo =
|
||||||
TorService.sharedInstance.getProxyInfo();
|
TorService.sharedInstance.getProxyInfo();
|
||||||
|
|
||||||
_httpClient = HttpClient();
|
_httpClient = HttpClient();
|
||||||
SocksTCPClient.assignToHttpClient(
|
SocksTCPClient.assignToHttpClient(
|
||||||
_httpClient!, [ProxySettings(proxyInfo.host, proxyInfo.port)]);
|
_httpClient,
|
||||||
} else {
|
[
|
||||||
_httpClient = null;
|
ProxySettings(
|
||||||
|
proxyInfo.host,
|
||||||
|
proxyInfo.port,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_stellarSdk = stellar.StellarSDK("${currentNode.host}:${currentNode.port}",
|
return stellar.StellarSDK(
|
||||||
httpClient: _httpClient);
|
"${currentNode.host}:${currentNode.port}",
|
||||||
|
httpClient: _httpClient,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _accountExists(String accountId) async {
|
Future<bool> _accountExists(String accountId) async {
|
||||||
bool exists = false;
|
bool exists = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final receiverAccount = await stellarSdk.accounts.account(accountId);
|
final receiverAccount =
|
||||||
|
await (await stellarSdk).accounts.account(accountId);
|
||||||
if (receiverAccount.accountId != "") {
|
if (receiverAccount.accountId != "") {
|
||||||
exists = true;
|
exists = true;
|
||||||
}
|
}
|
||||||
|
@ -183,7 +244,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
@override
|
@override
|
||||||
Future<TxData> confirmSend({required TxData txData}) async {
|
Future<TxData> confirmSend({required TxData txData}) async {
|
||||||
final senderKeyPair = await _getSenderKeyPair(index: 0);
|
final senderKeyPair = await _getSenderKeyPair(index: 0);
|
||||||
final sender = await stellarSdk.accounts.account(senderKeyPair.accountId);
|
final sender =
|
||||||
|
await (await stellarSdk).accounts.account(senderKeyPair.accountId);
|
||||||
|
|
||||||
final address = txData.recipients!.first.address;
|
final address = txData.recipients!.first.address;
|
||||||
final amountToSend = txData.recipients!.first.amount;
|
final amountToSend = txData.recipients!.first.amount;
|
||||||
|
@ -221,7 +283,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
|
|
||||||
transaction.sign(senderKeyPair, stellarNetwork);
|
transaction.sign(senderKeyPair, stellarNetwork);
|
||||||
try {
|
try {
|
||||||
final response = await stellarSdk.submitTransaction(transaction);
|
final response = await (await stellarSdk).submitTransaction(transaction);
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
throw Exception("${response.extras?.resultCodes?.transactionResultCode}"
|
throw Exception("${response.extras?.resultCodes?.transactionResultCode}"
|
||||||
" ::: ${response.extras?.resultCodes?.operationsResultCodes}");
|
" ::: ${response.extras?.resultCodes?.operationsResultCodes}");
|
||||||
|
@ -248,7 +310,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FeeObject> get fees async {
|
Future<FeeObject> get fees async {
|
||||||
int fee = await _getBaseFee();
|
final int fee = await _getBaseFee();
|
||||||
return FeeObject(
|
return FeeObject(
|
||||||
numberOfBlocksFast: 1,
|
numberOfBlocksFast: 1,
|
||||||
numberOfBlocksAverage: 1,
|
numberOfBlocksAverage: 1,
|
||||||
|
@ -286,7 +348,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
stellar.AccountResponse accountResponse;
|
stellar.AccountResponse accountResponse;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
accountResponse = await stellarSdk.accounts
|
accountResponse = await (await stellarSdk)
|
||||||
|
.accounts
|
||||||
.account((await getCurrentReceivingAddress())!.value)
|
.account((await getCurrentReceivingAddress())!.value)
|
||||||
.onError((error, stackTrace) => throw error!);
|
.onError((error, stackTrace) => throw error!);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -307,7 +370,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (stellar.Balance balance in accountResponse.balances) {
|
for (final stellar.Balance balance in accountResponse.balances) {
|
||||||
switch (balance.assetType) {
|
switch (balance.assetType) {
|
||||||
case stellar.Asset.TYPE_NATIVE:
|
case stellar.Asset.TYPE_NATIVE:
|
||||||
final swBalance = Balance(
|
final swBalance = Balance(
|
||||||
|
@ -344,7 +407,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
@override
|
@override
|
||||||
Future<void> updateChainHeight() async {
|
Future<void> updateChainHeight() async {
|
||||||
try {
|
try {
|
||||||
final height = await stellarSdk.ledgers
|
final height = await (await stellarSdk)
|
||||||
|
.ledgers
|
||||||
.order(stellar.RequestBuilderOrder.DESC)
|
.order(stellar.RequestBuilderOrder.DESC)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.execute()
|
.execute()
|
||||||
|
@ -362,7 +426,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateNode() async {
|
Future<void> updateNode() async {
|
||||||
_updateSdk();
|
_stellarSdk?.httpClient.close();
|
||||||
|
_stellarSdk = _getFreshSdk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -370,10 +435,11 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
try {
|
try {
|
||||||
final myAddress = (await getCurrentReceivingAddress())!;
|
final myAddress = (await getCurrentReceivingAddress())!;
|
||||||
|
|
||||||
List<TransactionV2> transactionList = [];
|
final List<TransactionV2> transactionList = [];
|
||||||
stellar.Page<stellar.OperationResponse> payments;
|
stellar.Page<stellar.OperationResponse> payments;
|
||||||
try {
|
try {
|
||||||
payments = await stellarSdk.payments
|
payments = await (await stellarSdk)
|
||||||
|
.payments
|
||||||
.forAccount(myAddress.value)
|
.forAccount(myAddress.value)
|
||||||
.order(stellar.RequestBuilderOrder.DESC)
|
.order(stellar.RequestBuilderOrder.DESC)
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -393,7 +459,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (stellar.OperationResponse response in payments.records!) {
|
for (final stellar.OperationResponse response in payments.records!) {
|
||||||
// PaymentOperationResponse por;
|
// PaymentOperationResponse por;
|
||||||
if (response is stellar.PaymentOperationResponse) {
|
if (response is stellar.PaymentOperationResponse) {
|
||||||
final por = response;
|
final por = response;
|
||||||
|
@ -423,7 +489,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
final List<OutputV2> outputs = [];
|
final List<OutputV2> outputs = [];
|
||||||
final List<InputV2> inputs = [];
|
final List<InputV2> inputs = [];
|
||||||
|
|
||||||
OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor(
|
final OutputV2 output =
|
||||||
|
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptPubKeyHex: "00",
|
scriptPubKeyHex: "00",
|
||||||
valueStringSats: amount.raw.toString(),
|
valueStringSats: amount.raw.toString(),
|
||||||
addresses: [
|
addresses: [
|
||||||
|
@ -431,7 +498,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
],
|
],
|
||||||
walletOwns: addressTo == myAddress.value,
|
walletOwns: addressTo == myAddress.value,
|
||||||
);
|
);
|
||||||
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptSigHex: null,
|
scriptSigHex: null,
|
||||||
scriptSigAsm: null,
|
scriptSigAsm: null,
|
||||||
sequence: null,
|
sequence: null,
|
||||||
|
@ -451,8 +518,9 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
int height = 0;
|
int height = 0;
|
||||||
//Query the transaction linked to the payment,
|
//Query the transaction linked to the payment,
|
||||||
// por.transaction returns a null sometimes
|
// por.transaction returns a null sometimes
|
||||||
stellar.TransactionResponse tx =
|
final stellar.TransactionResponse tx = await (await stellarSdk)
|
||||||
await stellarSdk.transactions.transaction(por.transactionHash!);
|
.transactions
|
||||||
|
.transaction(por.transactionHash!);
|
||||||
|
|
||||||
if (tx.hash.isNotEmpty) {
|
if (tx.hash.isNotEmpty) {
|
||||||
fee = tx.feeCharged!;
|
fee = tx.feeCharged!;
|
||||||
|
@ -503,7 +571,8 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
final List<OutputV2> outputs = [];
|
final List<OutputV2> outputs = [];
|
||||||
final List<InputV2> inputs = [];
|
final List<InputV2> inputs = [];
|
||||||
|
|
||||||
OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor(
|
final OutputV2 output =
|
||||||
|
OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptPubKeyHex: "00",
|
scriptPubKeyHex: "00",
|
||||||
valueStringSats: amount.raw.toString(),
|
valueStringSats: amount.raw.toString(),
|
||||||
addresses: [
|
addresses: [
|
||||||
|
@ -512,7 +581,7 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
],
|
],
|
||||||
walletOwns: caor.sourceAccount! == myAddress.value,
|
walletOwns: caor.sourceAccount! == myAddress.value,
|
||||||
);
|
);
|
||||||
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
scriptSigHex: null,
|
scriptSigHex: null,
|
||||||
scriptSigAsm: null,
|
scriptSigAsm: null,
|
||||||
sequence: null,
|
sequence: null,
|
||||||
|
@ -533,8 +602,9 @@ class StellarWallet extends Bip39Wallet<Stellar> {
|
||||||
|
|
||||||
int fee = 0;
|
int fee = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
final tx =
|
final tx = await (await stellarSdk)
|
||||||
await stellarSdk.transactions.transaction(caor.transactionHash!);
|
.transactions
|
||||||
|
.transaction(caor.transactionHash!);
|
||||||
if (tx.hash.isNotEmpty) {
|
if (tx.hash.isNotEmpty) {
|
||||||
fee = tx.feeCharged!;
|
fee = tx.feeCharged!;
|
||||||
height = tx.ledger;
|
height = tx.ledger;
|
||||||
|
|
56
lib/widgets/custom_buttons/checkbox_text_button.dart
Normal file
56
lib/widgets/custom_buttons/checkbox_text_button.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
|
||||||
|
class CheckboxTextButton extends StatefulWidget {
|
||||||
|
const CheckboxTextButton({super.key, required this.label, this.onChanged});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final void Function(bool)? onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CheckboxTextButton> createState() => _CheckboxTextButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CheckboxTextButtonState extends State<CheckboxTextButton> {
|
||||||
|
bool _value = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_value = !_value;
|
||||||
|
});
|
||||||
|
widget.onChanged?.call(_value);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 26,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
value: _value,
|
||||||
|
onChanged: (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.label,
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
43
lib/widgets/custom_buttons/frost_qr_dialog_button.dart
Normal file
43
lib/widgets/custom_buttons/frost_qr_dialog_button.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
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/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/frost/frost_step_qr_dialog.dart';
|
||||||
|
|
||||||
|
class FrostQrDialogPopupButton extends ConsumerWidget {
|
||||||
|
const FrostQrDialogPopupButton({super.key, required this.data});
|
||||||
|
|
||||||
|
final String data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return SecondaryButton(
|
||||||
|
label: "View QR code",
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.qrcode,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => FrostStepQrDialog(
|
||||||
|
myName: ref.read(pFrostMyName)!,
|
||||||
|
title: "Step "
|
||||||
|
"${ref.read(pFrostCreateCurrentStep)}"
|
||||||
|
" of "
|
||||||
|
"${ref.read(pFrostScaffoldArgs)!.stepRoutes.length}"
|
||||||
|
" - ${ref.read(pFrostScaffoldArgs)!.stepRoutes[ref.watch(pFrostCreateCurrentStep) - 1].title}",
|
||||||
|
data: data,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,22 +7,35 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class DetailItem extends StatelessWidget {
|
class DetailItem extends StatelessWidget {
|
||||||
const DetailItem({
|
const DetailItem({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.detail,
|
required this.detail,
|
||||||
this.button,
|
this.button,
|
||||||
|
this.overrideDetailTextColor,
|
||||||
this.showEmptyDetail = true,
|
this.showEmptyDetail = true,
|
||||||
|
this.horizontal = false,
|
||||||
this.disableSelectableText = false,
|
this.disableSelectableText = false,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String detail;
|
final String detail;
|
||||||
final Widget? button;
|
final Widget? button;
|
||||||
final bool showEmptyDetail;
|
final bool showEmptyDetail;
|
||||||
|
final bool horizontal;
|
||||||
final bool disableSelectableText;
|
final bool disableSelectableText;
|
||||||
|
final Color? overrideDetailTextColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final TextStyle detailStyle;
|
||||||
|
if (overrideDetailTextColor != null) {
|
||||||
|
detailStyle = STextStyles.w500_14(context).copyWith(
|
||||||
|
color: overrideDetailTextColor,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
detailStyle = STextStyles.w500_14(context);
|
||||||
|
}
|
||||||
|
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
condition: !Util.isDesktop,
|
condition: !Util.isDesktop,
|
||||||
builder: (child) => RoundedWhiteContainer(
|
builder: (child) => RoundedWhiteContainer(
|
||||||
|
@ -34,56 +47,80 @@ class DetailItem extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
child: Column(
|
child: horizontal
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Row(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
disableSelectableText
|
||||||
children: [
|
? Text(
|
||||||
disableSelectableText
|
title,
|
||||||
? Text(
|
style: STextStyles.itemSubtitle(context),
|
||||||
title,
|
)
|
||||||
style: STextStyles.itemSubtitle(context),
|
: SelectableText(
|
||||||
)
|
title,
|
||||||
: SelectableText(
|
style: STextStyles.itemSubtitle(context),
|
||||||
title,
|
|
||||||
style: STextStyles.itemSubtitle(context),
|
|
||||||
),
|
|
||||||
button ?? Container(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
detail.isEmpty && showEmptyDetail
|
|
||||||
? disableSelectableText
|
|
||||||
? Text(
|
|
||||||
"$title will appear here",
|
|
||||||
style: STextStyles.w500_14(context).copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.textSubtitle3,
|
|
||||||
),
|
),
|
||||||
)
|
disableSelectableText
|
||||||
: SelectableText(
|
? Text(
|
||||||
"$title will appear here",
|
detail,
|
||||||
style: STextStyles.w500_14(context).copyWith(
|
style: detailStyle,
|
||||||
color: Theme.of(context)
|
)
|
||||||
.extension<StackColors>()!
|
: SelectableText(
|
||||||
.textSubtitle3,
|
detail,
|
||||||
|
style: detailStyle,
|
||||||
),
|
),
|
||||||
)
|
],
|
||||||
: disableSelectableText
|
)
|
||||||
? Text(
|
: Column(
|
||||||
detail,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: STextStyles.w500_14(context),
|
children: [
|
||||||
)
|
Row(
|
||||||
: SelectableText(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
detail,
|
children: [
|
||||||
style: STextStyles.w500_14(context),
|
disableSelectableText
|
||||||
),
|
? Text(
|
||||||
],
|
title,
|
||||||
),
|
style: STextStyles.itemSubtitle(context),
|
||||||
|
)
|
||||||
|
: SelectableText(
|
||||||
|
title,
|
||||||
|
style: STextStyles.itemSubtitle(context),
|
||||||
|
),
|
||||||
|
button ?? Container(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
detail.isEmpty && showEmptyDetail
|
||||||
|
? disableSelectableText
|
||||||
|
? Text(
|
||||||
|
"$title will appear here",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SelectableText(
|
||||||
|
"$title will appear here",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: disableSelectableText
|
||||||
|
? Text(
|
||||||
|
detail,
|
||||||
|
style: detailStyle,
|
||||||
|
)
|
||||||
|
: SelectableText(
|
||||||
|
detail,
|
||||||
|
style: detailStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
209
lib/widgets/dialogs/frost/frost_step_qr_dialog.dart
Normal file
209
lib/widgets/dialogs/frost/frost_step_qr_dialog.dart
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:stackwallet/notifications/show_flush_bar.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/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class FrostStepQrDialog extends StatefulWidget {
|
||||||
|
const FrostStepQrDialog({
|
||||||
|
super.key,
|
||||||
|
required this.myName,
|
||||||
|
required this.title,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String myName;
|
||||||
|
final String title;
|
||||||
|
final String data;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FrostStepQrDialog> createState() => _FrostStepQrDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostStepQrDialogState extends State<FrostStepQrDialog> {
|
||||||
|
final _qrKey = GlobalKey();
|
||||||
|
|
||||||
|
Future<void> _capturePng(bool shouldSaveInsteadOfShare) async {
|
||||||
|
try {
|
||||||
|
final boundary =
|
||||||
|
_qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
|
||||||
|
final image = await boundary.toImage();
|
||||||
|
final byteData = await image.toByteData(format: ImageByteFormat.png);
|
||||||
|
final pngBytes = byteData!.buffer.asUint8List();
|
||||||
|
|
||||||
|
if (shouldSaveInsteadOfShare) {
|
||||||
|
if (Util.isDesktop) {
|
||||||
|
final dir = Directory("${Platform.environment['HOME']}");
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
throw Exception(
|
||||||
|
"Home dir not found while trying to open filepicker on QR image save");
|
||||||
|
}
|
||||||
|
final path = await FilePicker.platform.saveFile(
|
||||||
|
fileName: "qrcode.png",
|
||||||
|
initialDirectory: dir.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (path != null && context.mounted) {
|
||||||
|
final file = File(path);
|
||||||
|
if (file.existsSync()) {
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.warning,
|
||||||
|
message: "$path already exists!",
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await file.writeAsBytes(pngBytes);
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.success,
|
||||||
|
message: "$path saved!",
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// await DocumentFileSavePlus.saveFile(
|
||||||
|
// pngBytes,
|
||||||
|
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
|
||||||
|
// "image/png");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
final file = await File("${tempDir.path}/qrcode.png").create();
|
||||||
|
await file.writeAsBytes(pngBytes);
|
||||||
|
|
||||||
|
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
|
||||||
|
text: "Receive URI QR Code");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//todo: comeback to this
|
||||||
|
debugPrint(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SimpleMobileDialog(
|
||||||
|
showCloseButton: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
RepaintBoundary(
|
||||||
|
key: _qrKey,
|
||||||
|
child: RoundedWhiteContainer(
|
||||||
|
boxShadow: [
|
||||||
|
Theme.of(context).extension<StackColors>()!.standardBoxShadow
|
||||||
|
],
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.myName,
|
||||||
|
style: STextStyles.w600_16(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: STextStyles.w600_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
RoundedContainer(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textFieldDefaultBG,
|
||||||
|
radiusMultiplier: 1,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ConditionalParent(
|
||||||
|
condition: Util.isDesktop,
|
||||||
|
builder: (child) => ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 360,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: QrImageView(
|
||||||
|
data: widget.data,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.accentColorDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SelectableText(
|
||||||
|
widget.data,
|
||||||
|
style: STextStyles.w500_10(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop)
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
if (!Util.isDesktop)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Share",
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.share,
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.buttonTextSecondary,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await _capturePng(false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
|
|
||||||
enum FrostInterruptionDialogType {
|
|
||||||
walletCreation,
|
|
||||||
resharing,
|
|
||||||
transactionCreation;
|
|
||||||
}
|
|
||||||
|
|
||||||
class FrostInterruptionDialog extends StatelessWidget {
|
|
||||||
const FrostInterruptionDialog({
|
|
||||||
super.key,
|
|
||||||
required this.type,
|
|
||||||
required this.popUntilOnYesRouteName,
|
|
||||||
this.onNoPressedOverride,
|
|
||||||
this.onYesPressedOverride,
|
|
||||||
});
|
|
||||||
|
|
||||||
final FrostInterruptionDialogType type;
|
|
||||||
final String popUntilOnYesRouteName;
|
|
||||||
final VoidCallback? onNoPressedOverride;
|
|
||||||
final VoidCallback? onYesPressedOverride;
|
|
||||||
|
|
||||||
String get message {
|
|
||||||
switch (type) {
|
|
||||||
case FrostInterruptionDialogType.walletCreation:
|
|
||||||
return "wallet creation";
|
|
||||||
case FrostInterruptionDialogType.resharing:
|
|
||||||
return "resharing";
|
|
||||||
case FrostInterruptionDialogType.transactionCreation:
|
|
||||||
return "transaction signing";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return StackDialog(
|
|
||||||
title: "Cancel $message process",
|
|
||||||
message: "Are you sure you want to cancel the $message process?",
|
|
||||||
leftButton: SecondaryButton(
|
|
||||||
label: "No",
|
|
||||||
onPressed: onNoPressedOverride ??
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
|
||||||
rootNavigator: Util.isDesktop,
|
|
||||||
).pop,
|
|
||||||
),
|
|
||||||
rightButton: PrimaryButton(
|
|
||||||
label: "Yes",
|
|
||||||
onPressed: onYesPressedOverride ??
|
|
||||||
() {
|
|
||||||
// pop dialog
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
|
||||||
rootNavigator: Util.isDesktop,
|
|
||||||
).pop();
|
|
||||||
|
|
||||||
Navigator.of(context).popUntil(
|
|
||||||
ModalRoute.withName(
|
|
||||||
popUntilOnYesRouteName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
81
lib/widgets/dialogs/simple_mobile_dialog.dart
Normal file
81
lib/widgets/dialogs/simple_mobile_dialog.dart
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
|
||||||
|
class SimpleMobileDialog extends StatelessWidget {
|
||||||
|
const SimpleMobileDialog({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.showCloseButton = true,
|
||||||
|
this.padding,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final bool showCloseButton;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Material(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: padding ?? const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showCloseButton)
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
if (showCloseButton)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Close",
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,9 +9,11 @@ class FeeSlider extends StatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
required this.onSatVByteChanged,
|
required this.onSatVByteChanged,
|
||||||
required this.coin,
|
required this.coin,
|
||||||
|
this.showWU = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Coin coin;
|
final Coin coin;
|
||||||
|
final bool showWU;
|
||||||
final void Function(int) onSatVByteChanged;
|
final void Function(int) onSatVByteChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -34,7 +36,7 @@ class _FeeSliderState extends State<FeeSlider> {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"sat/vByte",
|
widget.showWU ? "sat/WU" : "sat/vByte",
|
||||||
style: STextStyles.smallMed12(context),
|
style: STextStyles.smallMed12(context),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
|
|
|
@ -9,15 +9,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_step_explanation_dialog.dart';
|
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/frost/frost_step_explanation_dialog.dart';
|
||||||
|
|
||||||
class FrostMascot extends StatelessWidget {
|
class FrostMascot extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final String body;
|
final String body;
|
||||||
FrostMascot({
|
const FrostMascot({
|
||||||
super.key,
|
super.key,
|
||||||
this.onPressed, required this.title, required this.body,
|
this.onPressed,
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
@ -32,8 +34,10 @@ class FrostMascot extends StatelessWidget {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => FrostStepExplanationDialog(
|
||||||
FrostStepExplanationDialog(title: title, body: body),
|
title: title,
|
||||||
|
body: body,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Image(
|
child: Image(
|
223
lib/widgets/frost_scaffold.dart
Normal file
223
lib/widgets/frost_scaffold.dart
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.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/progress_bar.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
|
class FrostStepScaffold extends ConsumerStatefulWidget {
|
||||||
|
const FrostStepScaffold({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/frostStepScaffold";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostStepScaffold> createState() => _FrostScaffoldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostScaffoldState extends ConsumerState<FrostStepScaffold> {
|
||||||
|
static const _titleTextSize = 18.0;
|
||||||
|
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
late final List<FrostStepRoute> _routes;
|
||||||
|
|
||||||
|
bool _requestPopLock = false;
|
||||||
|
|
||||||
|
String get _message {
|
||||||
|
switch (ref.read(pFrostScaffoldArgs)!.frostInterruptionDialogType) {
|
||||||
|
case FrostInterruptionDialogType.walletCreation:
|
||||||
|
return "wallet creation";
|
||||||
|
case FrostInterruptionDialogType.resharing:
|
||||||
|
return "resharing";
|
||||||
|
case FrostInterruptionDialogType.transactionCreation:
|
||||||
|
return "transaction signing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _requestPop(BuildContext context) async {
|
||||||
|
if (_requestPopLock ||
|
||||||
|
(Util.isDesktop && ref.read(pFrostScaffoldCanPopDesktop))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_requestPopLock = true;
|
||||||
|
|
||||||
|
final resultFuture = showDialog<String?>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => StackDialog(
|
||||||
|
title: "Cancel $_message process",
|
||||||
|
message: "Are you sure you want to cancel the $_message process?",
|
||||||
|
leftButton: SecondaryButton(
|
||||||
|
label: "No",
|
||||||
|
onPressed: () {
|
||||||
|
// pop dialog
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: Util.isDesktop,
|
||||||
|
).pop("no");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
rightButton: PrimaryButton(
|
||||||
|
label: "Yes",
|
||||||
|
onPressed: () {
|
||||||
|
// pop dialog
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
rootNavigator: Util.isDesktop,
|
||||||
|
).pop("yes");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// make sure to at least delay some time otherwise flutter pops back more than a single route lol...
|
||||||
|
final minTimeFuture =
|
||||||
|
Future<void>.delayed(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
|
final result = await Future.wait<dynamic>([resultFuture, minTimeFuture]);
|
||||||
|
|
||||||
|
if (context.mounted && result[0] == "yes") {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ref.read(pFrostScaffoldArgs.state).state = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestPopLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_routes = ref.read(pFrostScaffoldArgs)!.stepRoutes;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: Util.isDesktop && ref.watch(pFrostScaffoldCanPopDesktop),
|
||||||
|
onPopInvoked: (_) => _requestPop(context),
|
||||||
|
child: Material(
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: Util.isDesktop,
|
||||||
|
builder: (child) => child,
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: !Util.isDesktop,
|
||||||
|
builder: (child) => Background(
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
body: SafeArea(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// header
|
||||||
|
SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${ref.watch(pFrostCreateCurrentStep)} / ${_routes.length}",
|
||||||
|
style: STextStyles.navBarTitle(context).copyWith(
|
||||||
|
fontSize: _titleTextSize,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_routes[ref.watch(pFrostCreateCurrentStep) - 1]
|
||||||
|
.title,
|
||||||
|
style: STextStyles.navBarTitle(context).copyWith(
|
||||||
|
fontSize: _titleTextSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
text: "Exit",
|
||||||
|
textSize: _titleTextSize,
|
||||||
|
onTap: () => _requestPop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (subContext, constraints) => ProgressBar(
|
||||||
|
width: constraints.maxWidth,
|
||||||
|
height: 3,
|
||||||
|
fillColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText,
|
||||||
|
backgroundColor: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.customTextButtonEnabledText
|
||||||
|
.withOpacity(0.1),
|
||||||
|
percent:
|
||||||
|
ref.watch(pFrostCreateCurrentStep) / _routes.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: Util.isDesktop,
|
||||||
|
builder: (child) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 500,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: !Util.isDesktop,
|
||||||
|
builder: (child) => LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: constraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: Navigator(
|
||||||
|
key: _navigatorKey,
|
||||||
|
initialRoute: _routes[0].routeName,
|
||||||
|
onGenerateRoute: FrostRouteGenerator.generateRoute,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
46
lib/widgets/frost_step_user_steps.dart
Normal file
46
lib/widgets/frost_step_user_steps.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
|
class FrostStepUserSteps extends StatelessWidget {
|
||||||
|
const FrostStepUserSteps({super.key, required this.userSteps});
|
||||||
|
|
||||||
|
final List<String> userSteps;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < userSteps.length; i++)
|
||||||
|
ConditionalParent(
|
||||||
|
condition: i > 0,
|
||||||
|
builder: (child) => Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${i + 1}.",
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
userSteps[i],
|
||||||
|
style: STextStyles.w500_12(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
182
lib/widgets/textfields/frost_step_field.dart
Normal file
182
lib/widgets/textfields/frost_step_field.dart
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.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/textfield_icon_button.dart';
|
||||||
|
|
||||||
|
class FrostStepField extends StatefulWidget {
|
||||||
|
const FrostStepField({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.focusNode,
|
||||||
|
this.label,
|
||||||
|
this.hint,
|
||||||
|
required this.onChanged,
|
||||||
|
required this.showQrScanOption,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
final FocusNode focusNode;
|
||||||
|
final String? label;
|
||||||
|
final String? hint;
|
||||||
|
final void Function(String) onChanged;
|
||||||
|
final bool showQrScanOption;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FrostStepField> createState() => _FrostStepFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostStepFieldState extends State<FrostStepField> {
|
||||||
|
final _xKey = UniqueKey();
|
||||||
|
final _pasteKey = UniqueKey();
|
||||||
|
late final Key? _qrKey;
|
||||||
|
|
||||||
|
bool _isEmpty = true;
|
||||||
|
|
||||||
|
final _inputBorder = OutlineInputBorder(
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
width: 0,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
Constants.size.circularBorderRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
late final void Function(String) _changed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_qrKey = widget.showQrScanOption ? UniqueKey() : null;
|
||||||
|
_isEmpty = widget.controller.text.isEmpty;
|
||||||
|
|
||||||
|
_changed = (value) {
|
||||||
|
if (context.mounted) {
|
||||||
|
widget.onChanged.call(value);
|
||||||
|
setState(() {
|
||||||
|
_isEmpty = widget.controller.text.isEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ConditionalParent(
|
||||||
|
condition: widget.label != null,
|
||||||
|
builder: (child) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.label!,
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: widget.controller,
|
||||||
|
focusNode: widget.focusNode,
|
||||||
|
readOnly: false,
|
||||||
|
autocorrect: false,
|
||||||
|
enableSuggestions: false,
|
||||||
|
style: STextStyles.field(context),
|
||||||
|
onChanged: _changed,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: widget.hint,
|
||||||
|
fillColor: widget.focusNode.hasFocus
|
||||||
|
? Theme.of(context).extension<StackColors>()!.textFieldActiveBG
|
||||||
|
: Theme.of(context).extension<StackColors>()!.textFieldDefaultBG,
|
||||||
|
hintStyle: Util.isDesktop
|
||||||
|
? STextStyles.desktopTextFieldLabel(context)
|
||||||
|
: STextStyles.fieldLabel(context),
|
||||||
|
enabledBorder: _inputBorder,
|
||||||
|
focusedBorder: _inputBorder,
|
||||||
|
errorBorder: _inputBorder,
|
||||||
|
disabledBorder: _inputBorder,
|
||||||
|
focusedErrorBorder: _inputBorder,
|
||||||
|
suffixIcon: Padding(
|
||||||
|
padding: _isEmpty
|
||||||
|
? const EdgeInsets.only(right: 8)
|
||||||
|
: const EdgeInsets.only(right: 0),
|
||||||
|
child: UnconstrainedBox(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
!_isEmpty
|
||||||
|
? TextFieldIconButton(
|
||||||
|
semanticsLabel:
|
||||||
|
"Clear Button. Clears The Frost Step Field Input.",
|
||||||
|
key: _xKey,
|
||||||
|
onTap: () {
|
||||||
|
widget.controller.text = "";
|
||||||
|
|
||||||
|
_changed(widget.controller.text);
|
||||||
|
},
|
||||||
|
child: const XIcon(),
|
||||||
|
)
|
||||||
|
: TextFieldIconButton(
|
||||||
|
semanticsLabel:
|
||||||
|
"Paste Button. Pastes From Clipboard To Frost Step Field Input.",
|
||||||
|
key: _pasteKey,
|
||||||
|
onTap: () async {
|
||||||
|
final ClipboardData? data =
|
||||||
|
await Clipboard.getData(Clipboard.kTextPlain);
|
||||||
|
if (data?.text != null && data!.text!.isNotEmpty) {
|
||||||
|
widget.controller.text = data.text!.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
_changed(widget.controller.text);
|
||||||
|
},
|
||||||
|
child:
|
||||||
|
_isEmpty ? const ClipboardIcon() : const XIcon(),
|
||||||
|
),
|
||||||
|
if (_isEmpty && widget.showQrScanOption)
|
||||||
|
TextFieldIconButton(
|
||||||
|
semanticsLabel:
|
||||||
|
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
||||||
|
key: _qrKey,
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
if (FocusScope.of(context).hasFocus) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
await Future<void>.delayed(
|
||||||
|
const Duration(milliseconds: 75));
|
||||||
|
}
|
||||||
|
|
||||||
|
final qrResult = await BarcodeScanner.scan();
|
||||||
|
|
||||||
|
widget.controller.text = qrResult.rawContent;
|
||||||
|
|
||||||
|
_changed(widget.controller.text);
|
||||||
|
} 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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,7 +105,7 @@ class SimpleWalletCard extends ConsumerWidget {
|
||||||
whileFuture: loadFuture,
|
whileFuture: loadFuture,
|
||||||
context: context,
|
context: context,
|
||||||
message: 'Opening ${wallet.info.name}',
|
message: 'Opening ${wallet.info.name}',
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
);
|
);
|
||||||
if (popPrevious) nav.pop();
|
if (popPrevious) nav.pop();
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ class SimpleWalletCard extends ConsumerWidget {
|
||||||
context: desktopNavigatorState?.context ?? context,
|
context: desktopNavigatorState?.context ?? context,
|
||||||
opaqueBG: true,
|
opaqueBG: true,
|
||||||
message: "Loading ${contract.name}",
|
message: "Loading ${contract.name}",
|
||||||
isDesktop: Util.isDesktop,
|
rootNavigator: Util.isDesktop,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success!) {
|
if (!success!) {
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Stack Wallet.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023 Cypher Stack
|
||||||
|
* All Rights Reserved.
|
||||||
|
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||||
|
* Generated by Cypher Stack on 2023-05-26
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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/themes/theme_providers.dart';
|
||||||
|
|
||||||
|
class FrostSignNavIcon extends ConsumerWidget {
|
||||||
|
const FrostSignNavIcon({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return SvgPicture.file(
|
||||||
|
File(
|
||||||
|
ref.watch(
|
||||||
|
themeProvider.select(
|
||||||
|
// TODO: [prio=high] update themes with icon asset
|
||||||
|
(value) => value.assets.stackIcon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue