mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-02 03:06:29 +00:00
rough gui refactor for frost wallet creation on mobile
This commit is contained in:
parent
3e74683d6c
commit
3c76cc115c
29 changed files with 2030 additions and 1406 deletions
|
@ -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.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
177
lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart
Normal file
177
lib/pages/add_wallet_views/frost_ms/frost_scaffold.dart
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/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/progress_bar.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;
|
||||||
|
Future<void> _requestPop(BuildContext context) async {
|
||||||
|
if (_requestPopLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_requestPopLock = true;
|
||||||
|
|
||||||
|
// TODO: dialog to confirm exit
|
||||||
|
|
||||||
|
// make sure to at least delay some time otherwise flutter pops back more than a single route lol...
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ref.read(pFrostCreateNewArgs.state).state = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestPopLock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_routes = ref.read(pFrostCreateNewArgs)!.$2;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopScope(
|
||||||
|
canPop: false,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,337 +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/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/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
|
||||||
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
|
|
||||||
import 'package:stackwallet/wallets/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/frost_mascot.dart';
|
|
||||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
|
||||||
|
|
||||||
class ConfirmNewFrostMSWalletCreationView extends ConsumerStatefulWidget {
|
|
||||||
const ConfirmNewFrostMSWalletCreationView({
|
|
||||||
super.key,
|
|
||||||
required this.walletName,
|
|
||||||
required this.frostCurrency,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/confirmNewFrostMSWalletCreationView";
|
|
||||||
|
|
||||||
final String walletName;
|
|
||||||
final FrostCurrency frostCurrency;
|
|
||||||
|
|
||||||
@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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// TODO: [prio=high] get rid of placeholder text??
|
|
||||||
trailing: const 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.frostCurrency.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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,10 +11,13 @@ import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.
|
||||||
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_mascot.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 {
|
||||||
|
@ -67,13 +70,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 +106,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();
|
||||||
|
@ -146,7 +175,7 @@ class _NewFrostMsWalletViewState
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"New FROST multisig config",
|
"Create new group",
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -173,10 +202,22 @@ class _NewFrostMsWalletViewState
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Threshold",
|
"Threshold",
|
||||||
style: STextStyles.label(context),
|
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,
|
||||||
|
@ -185,22 +226,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,
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
controller: _participantsController,
|
controller: _participantsController,
|
||||||
onChanged: _participantsCountChanged,
|
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,
|
||||||
|
@ -208,25 +280,76 @@ 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)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: controllers.first,
|
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)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Remaining participants",
|
"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),
|
style: STextStyles.label(context),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (controllers.length > 1)
|
if (controllers.length > 1)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -237,6 +360,10 @@ class _NewFrostMsWalletViewState
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controllers[i],
|
controller: controllers[i],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter name",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -246,7 +373,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();
|
||||||
|
@ -265,12 +392,13 @@ 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(
|
await Navigator.of(context).pushNamed(
|
||||||
|
|
|
@ -1,438 +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/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/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/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/frost_mascot.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostShareCommitmentsView extends ConsumerStatefulWidget {
|
|
||||||
const FrostShareCommitmentsView({
|
|
||||||
super.key,
|
|
||||||
required this.walletName,
|
|
||||||
required this.frostCurrency,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostShareCommitmentsView";
|
|
||||||
|
|
||||||
final String walletName;
|
|
||||||
final FrostCurrency frostCurrency;
|
|
||||||
|
|
||||||
@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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// TODO: [prio=high] get rid of placeholder text??
|
|
||||||
trailing: const 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,
|
|
||||||
frostCurrency: widget.frostCurrency,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} 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,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/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/logger.dart';
|
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
|
||||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
|
||||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
|
||||||
import 'package:stackwallet/widgets/custom_buttons/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/frost_mascot.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostShareSharesView extends ConsumerStatefulWidget {
|
|
||||||
const FrostShareSharesView({
|
|
||||||
super.key,
|
|
||||||
required this.walletName,
|
|
||||||
required this.frostCurrency,
|
|
||||||
});
|
|
||||||
|
|
||||||
static const String routeName = "/frostShareSharesView";
|
|
||||||
|
|
||||||
final String walletName;
|
|
||||||
final FrostCurrency frostCurrency;
|
|
||||||
|
|
||||||
@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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// TODO: [prio=high] get rid of placeholder text??
|
|
||||||
trailing: const 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,
|
|
||||||
frostCurrency: widget.frostCurrency,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} 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,11 +1,18 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||||
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/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||||
|
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
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/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
@ -374,12 +381,47 @@ class _ImportNewFrostMsWalletViewState
|
||||||
myName: ref.read(pFrostMyName.state).state!,
|
myName: ref.read(pFrostMyName.state).state!,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
ref.read(pFrostCreateNewArgs.state).state = (
|
||||||
FrostShareCommitmentsView.routeName,
|
(
|
||||||
arguments: (
|
|
||||||
walletName: widget.walletName,
|
walletName: widget.walletName,
|
||||||
frostCurrency: widget.frostCurrency,
|
frostCurrency: widget.frostCurrency,
|
||||||
),
|
),
|
||||||
|
FrostRouteGenerator.createNewConfigStepRoutes,
|
||||||
|
() {
|
||||||
|
// successful completion of steps
|
||||||
|
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;
|
||||||
|
ref.read(pFrostCreateNewArgs.state).state = null;
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.success,
|
||||||
|
message: "Your wallet is set up.",
|
||||||
|
iconAsset: Assets.svg.check,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostStepScaffold.routeName,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,7 +14,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/desktop/secondary_button.dart';
|
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class SelectNewFrostImportTypeView extends StatefulWidget {
|
class SelectNewFrostImportTypeView extends StatefulWidget {
|
||||||
|
@ -268,33 +268,7 @@ class _FrostJoinInfoDialog extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SafeArea(
|
return SimpleMobileDialog(
|
||||||
child: Column(
|
|
||||||
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: const EdgeInsets.all(24),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -361,34 +335,6 @@ class _FrostJoinInfoDialog extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Spacer(),
|
|
||||||
const SizedBox(
|
|
||||||
width: 16,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: SecondaryButton(
|
|
||||||
label: "Close",
|
|
||||||
onPressed: Navigator.of(context).pop,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_commitments_view.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_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/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/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/crypto_currency/intermediate/frost_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||||
|
@ -16,8 +24,11 @@ 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_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/desktop/secondary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/frost_mascot.dart';
|
import 'package:stackwallet/widgets/frost_mascot.dart';
|
||||||
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
|
||||||
class ShareNewMultisigConfigView extends ConsumerStatefulWidget {
|
class ShareNewMultisigConfigView extends ConsumerStatefulWidget {
|
||||||
const ShareNewMultisigConfigView({
|
const ShareNewMultisigConfigView({
|
||||||
|
@ -38,6 +49,114 @@ class ShareNewMultisigConfigView extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _ShareNewMultisigConfigViewState
|
class _ShareNewMultisigConfigViewState
|
||||||
extends ConsumerState<ShareNewMultisigConfigView> {
|
extends ConsumerState<ShareNewMultisigConfigView> {
|
||||||
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
|
@ -72,7 +191,7 @@ class _ShareNewMultisigConfigViewState
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
"Multisig config",
|
"Share multisig group info",
|
||||||
style: STextStyles.navBarTitle(context),
|
style: STextStyles.navBarTitle(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -99,7 +218,10 @@ class _ShareNewMultisigConfigViewState
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (!Util.isDesktop) const Spacer(),
|
const _SharingStepsInfo(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 220,
|
height: 220,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -119,7 +241,7 @@ class _ShareNewMultisigConfigViewState
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 32,
|
height: 20,
|
||||||
),
|
),
|
||||||
DetailItem(
|
DetailItem(
|
||||||
title: "Encoded config",
|
title: "Encoded config",
|
||||||
|
@ -137,12 +259,64 @@ class _ShareNewMultisigConfigViewState
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 64 : 16,
|
height: Util.isDesktop ? 64 : 16,
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SecondaryButton(
|
||||||
|
label: "Show group participants",
|
||||||
|
onPressed: _showParticipantsDialog,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
if (!Util.isDesktop)
|
if (!Util.isDesktop)
|
||||||
const Spacer(
|
const Spacer(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = !_userVerifyContinue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 26,
|
||||||
|
child: Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
value: _userVerifyContinue,
|
||||||
|
onChanged: (value) => setState(
|
||||||
|
() => _userVerifyContinue = value == true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"I have verified that everyone has joined the group",
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Start key generation",
|
label: "Start key generation",
|
||||||
|
enabled: _userVerifyContinue,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
ref.read(pFrostStartKeyGenData.notifier).state =
|
ref.read(pFrostStartKeyGenData.notifier).state =
|
||||||
Frost.startKeyGeneration(
|
Frost.startKeyGeneration(
|
||||||
|
@ -150,12 +324,48 @@ class _ShareNewMultisigConfigViewState
|
||||||
myName: ref.read(pFrostMyName.state).state!,
|
myName: ref.read(pFrostMyName.state).state!,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Navigator.of(context).pushNamed(
|
ref.read(pFrostCreateNewArgs.state).state = (
|
||||||
FrostShareCommitmentsView.routeName,
|
(
|
||||||
arguments: (
|
|
||||||
walletName: widget.walletName,
|
walletName: widget.walletName,
|
||||||
frostCurrency: widget.frostCurrency,
|
frostCurrency: widget.frostCurrency,
|
||||||
),
|
),
|
||||||
|
FrostRouteGenerator.createNewConfigStepRoutes,
|
||||||
|
() {
|
||||||
|
// successful completion of steps
|
||||||
|
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;
|
||||||
|
ref.read(pFrostCreateNewArgs.state).state = null;
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.success,
|
||||||
|
message: "Your wallet is set up.",
|
||||||
|
iconAsset: Assets.svg.check,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostStepScaffold.routeName,
|
||||||
|
// FrostShareCommitmentsView.routeName,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -165,3 +375,45 @@ class _ShareNewMultisigConfigViewState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SharingStepsInfo extends StatelessWidget {
|
||||||
|
const _SharingStepsInfo({super.key});
|
||||||
|
|
||||||
|
static const steps = [
|
||||||
|
"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”.",
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final style = STextStyles.w500_12(context);
|
||||||
|
return RoundedWhiteContainer(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < steps.length; i++)
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${i + 1}.",
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
steps[i],
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,370 @@
|
||||||
|
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:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/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/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/frost/frost_step_qr_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
|
class FrostCreateStep1 extends ConsumerStatefulWidget {
|
||||||
|
const FrostCreateStep1({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const String routeName = "/frostCreateStep1";
|
||||||
|
static const String title = "Commitments";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostCreateStep1> createState() => _FrostCreateStep1State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep1State extends ConsumerState<FrostCreateStep1> {
|
||||||
|
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;
|
||||||
|
|
||||||
|
Future<void> _showQrCodeDialog() async {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => FrostStepQrDialog(
|
||||||
|
myName: ref.read(pFrostMyName)!,
|
||||||
|
title: "Step 1 of 4 - ${FrostCreateStep1.title}",
|
||||||
|
data: myCommitment,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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),
|
||||||
|
SecondaryButton(
|
||||||
|
label: "View QR code",
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.qrcode,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: _showQrCodeDialog,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
for (int i = 0; i < participants.length; i++)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
participants[i],
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
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 SizedBox(height: 12),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = !_userVerifyContinue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 26,
|
||||||
|
child: Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
value: _userVerifyContinue,
|
||||||
|
onChanged: (value) => setState(
|
||||||
|
() => _userVerifyContinue = value == true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"I have verified that everyone has all commitments",
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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 = 2;
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostCreateStep2.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,320 @@
|
||||||
|
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:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||||
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
import 'package:stackwallet/services/frost.dart';
|
||||||
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
|
import 'package:stackwallet/widgets/dialogs/frost/frost_step_qr_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||||
|
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||||
|
|
||||||
|
class FrostCreateStep2 extends ConsumerStatefulWidget {
|
||||||
|
const FrostCreateStep2({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/frostCreateStep2";
|
||||||
|
static const String title = "Shares";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostCreateStep2> createState() => _FrostCreateStep2State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep2State extends ConsumerState<FrostCreateStep2> {
|
||||||
|
static const info = [
|
||||||
|
"Send your share to other group members.",
|
||||||
|
"Enter their shares into the corresponding fields.",
|
||||||
|
];
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
Future<void> _showQrCodeDialog() async {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => FrostStepQrDialog(
|
||||||
|
myName: ref.read(pFrostMyName)!,
|
||||||
|
title: "Step 2 of 4 - ${FrostCreateStep2.title}",
|
||||||
|
data: myShare,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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),
|
||||||
|
SecondaryButton(
|
||||||
|
label: "View QR code",
|
||||||
|
icon: SvgPicture.asset(
|
||||||
|
Assets.svg.qrcode,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: _showQrCodeDialog,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
for (int i = 0; i < participants.length; i++)
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
participants[i],
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
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 SizedBox(height: 12),
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.read(pFrostCreateCurrentStep.state).state = 3;
|
||||||
|
await Navigator.of(context).pushNamed(
|
||||||
|
FrostCreateStep3.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,75 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||||
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
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 FrostCreateStep3 extends ConsumerStatefulWidget {
|
||||||
|
const FrostCreateStep3({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/frostCreateStep3";
|
||||||
|
static const String title = "Verify multisig ID";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostCreateStep3> createState() => _FrostCreateStep3State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep3State extends ConsumerState<FrostCreateStep3> {
|
||||||
|
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 = 4;
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
FrostCreateStep4.routeName,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||||
|
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||||
|
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||||
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
|
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/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/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 FrostCreateStep4 extends ConsumerStatefulWidget {
|
||||||
|
const FrostCreateStep4({super.key});
|
||||||
|
|
||||||
|
static const String routeName = "/frostCreateStep4";
|
||||||
|
static const String title = "Back up your keys";
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<FrostCreateStep4> createState() => _FrostCreateStep4State();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FrostCreateStep4State extends ConsumerState<FrostCreateStep4> {
|
||||||
|
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),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = !_userVerifyContinue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 26,
|
||||||
|
child: Checkbox(
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
value: _userVerifyContinue,
|
||||||
|
onChanged: (value) => setState(
|
||||||
|
() => _userVerifyContinue = value == true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"I have backed up my keys and the config",
|
||||||
|
style: STextStyles.w500_14(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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(pFrostCreateNewArgs)!;
|
||||||
|
|
||||||
|
final info = WalletInfo.createNew(
|
||||||
|
coin: data.$1.frostCurrency.coin,
|
||||||
|
name: data.$1.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(pFrostCreateNewArgs)!.$3();
|
||||||
|
}
|
||||||
|
} 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,78 @@
|
||||||
|
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_1.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/route_generator.dart';
|
||||||
|
import 'package:stackwallet/wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||||
|
|
||||||
|
typedef FrostStepRoute = ({String routeName, String title});
|
||||||
|
|
||||||
|
final pFrostCreateCurrentStep = StateProvider.autoDispose((ref) => 1);
|
||||||
|
final pFrostCreateNewArgs = StateProvider<
|
||||||
|
(
|
||||||
|
({String walletName, FrostCurrency frostCurrency}),
|
||||||
|
List<FrostStepRoute>,
|
||||||
|
VoidCallback,
|
||||||
|
)?>((ref) => null);
|
||||||
|
|
||||||
|
abstract class FrostRouteGenerator {
|
||||||
|
static const bool useMaterialPageRoute = true;
|
||||||
|
|
||||||
|
static const List<FrostStepRoute> createNewConfigStepRoutes = [
|
||||||
|
(routeName: FrostCreateStep1.routeName, title: FrostCreateStep1.title),
|
||||||
|
(routeName: FrostCreateStep2.routeName, title: FrostCreateStep2.title),
|
||||||
|
(routeName: FrostCreateStep3.routeName, title: FrostCreateStep3.title),
|
||||||
|
(routeName: FrostCreateStep4.routeName, title: FrostCreateStep4.title),
|
||||||
|
];
|
||||||
|
|
||||||
|
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||||
|
final args = settings.arguments;
|
||||||
|
|
||||||
|
switch (settings.name) {
|
||||||
|
case FrostCreateStep1.routeName:
|
||||||
|
return RouteGenerator.getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => const FrostCreateStep1(),
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return _routeError("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Route<dynamic> _routeError(String message) {
|
||||||
|
return RouteGenerator.getRoute(
|
||||||
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
builder: (_) => Placeholder(
|
||||||
|
child: Center(
|
||||||
|
child: Text(message),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -413,7 +413,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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/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';
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/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';
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
import 'package:stackwallet/widgets/icon_widgets/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';
|
||||||
|
|
|
@ -17,7 +17,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/frost_mascot.dart';
|
import 'package:stackwallet/widgets/frost_mascot.dart';
|
||||||
|
|
||||||
class NewContinueSharingView extends ConsumerStatefulWidget {
|
class NewContinueSharingView extends ConsumerStatefulWidget {
|
||||||
|
|
|
@ -18,7 +18,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/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/frost_mascot.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';
|
||||||
|
|
|
@ -26,7 +26,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/dialogs/frost_interruption_dialog.dart';
|
import 'package:stackwallet/widgets/dialogs/frost/frost_interruption_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
class VerifyUpdatedWalletView extends ConsumerStatefulWidget {
|
class VerifyUpdatedWalletView extends ConsumerStatefulWidget {
|
||||||
|
|
|
@ -26,10 +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/frost_scaffold.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart';
|
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart';
|
||||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/frost_share_commitments_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/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/new/select_new_frost_import_type_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/new/share_new_multisig_config_view.dart';
|
||||||
|
@ -585,59 +583,14 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||||
|
|
||||||
case FrostShareCommitmentsView.routeName:
|
case FrostStepScaffold.routeName:
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
FrostCurrency frostCurrency,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
builder: (_) => FrostShareCommitmentsView(
|
builder: (_) => const FrostStepScaffold(),
|
||||||
walletName: args.walletName,
|
|
||||||
frostCurrency: args.frostCurrency,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
settings: RouteSettings(
|
||||||
name: settings.name,
|
name: settings.name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case FrostShareSharesView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
FrostCurrency frostCurrency,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => FrostShareSharesView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
frostCurrency: args.frostCurrency,
|
|
||||||
),
|
|
||||||
settings: RouteSettings(
|
|
||||||
name: settings.name,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
|
||||||
|
|
||||||
case ConfirmNewFrostMSWalletCreationView.routeName:
|
|
||||||
if (args is ({
|
|
||||||
String walletName,
|
|
||||||
FrostCurrency frostCurrency,
|
|
||||||
})) {
|
|
||||||
return getRoute(
|
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
|
||||||
builder: (_) => ConfirmNewFrostMSWalletCreationView(
|
|
||||||
walletName: args.walletName,
|
|
||||||
frostCurrency: args.frostCurrency,
|
|
||||||
),
|
|
||||||
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) {
|
||||||
|
|
|
@ -12,9 +12,9 @@ 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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
199
lib/widgets/dialogs/frost/frost_step_qr_dialog.dart
Normal file
199
lib/widgets/dialogs/frost/frost_step_qr_dialog.dart
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
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/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: [
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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;
|
||||||
|
|
38
lib/widgets/frost_step_user_steps.dart
Normal file
38
lib/widgets/frost_step_user_steps.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.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++)
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue