mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 12:19:24 +00:00
refactor "new ms config import" to step 1b
This commit is contained in:
parent
56b18a6c93
commit
999df80e60
6 changed files with 451 additions and 469 deletions
|
@ -1,433 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/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/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/wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/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 ImportNewFrostMsWalletView extends ConsumerStatefulWidget {
|
||||
const ImportNewFrostMsWalletView({
|
||||
super.key,
|
||||
required this.walletName,
|
||||
required this.frostCurrency,
|
||||
});
|
||||
|
||||
static const String routeName = "/importNewFrostMsWalletView";
|
||||
|
||||
final String walletName;
|
||||
final FrostCurrency frostCurrency;
|
||||
|
||||
@override
|
||||
ConsumerState<ImportNewFrostMsWalletView> createState() =>
|
||||
_ImportNewFrostMsWalletViewState();
|
||||
}
|
||||
|
||||
class _ImportNewFrostMsWalletViewState
|
||||
extends ConsumerState<ImportNewFrostMsWalletView> {
|
||||
late final TextEditingController myNameFieldController, configFieldController;
|
||||
late final FocusNode myNameFocusNode, configFocusNode;
|
||||
|
||||
bool _nameEmpty = true, _configEmpty = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
myNameFieldController = TextEditingController();
|
||||
configFieldController = TextEditingController();
|
||||
myNameFocusNode = FocusNode();
|
||||
configFocusNode = FocusNode();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myNameFieldController.dispose();
|
||||
configFieldController.dispose();
|
||||
myNameFocusNode.dispose();
|
||||
configFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => DesktopScaffold(
|
||||
background: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: const DesktopAppBar(
|
||||
isCompactHeight: false,
|
||||
leading: AppBarBackButton(),
|
||||
// TODO: [prio=high] get rid of placeholder text??
|
||||
trailing: FrostMascot(
|
||||
title: 'Lorem ipsum',
|
||||
body:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam est justo, ',
|
||||
),
|
||||
),
|
||||
body: SizedBox(
|
||||
width: 480,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Import FROST multisig config",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("frMyNameTextFieldKey"),
|
||||
controller: myNameFieldController,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
_nameEmpty = myNameFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
focusNode: myNameFocusNode,
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"My name",
|
||||
myNameFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: _nameEmpty
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!_nameEmpty
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears The Config Field.",
|
||||
key: const Key("frMyNameClearButtonKey"),
|
||||
onTap: () {
|
||||
myNameFieldController.text = "";
|
||||
|
||||
setState(() {
|
||||
_nameEmpty = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From Clipboard To Name Field.",
|
||||
key: const Key("frMyNamePasteButtonKey"),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
myNameFieldController.text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_nameEmpty =
|
||||
myNameFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: _nameEmpty
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("frConfigTextFieldKey"),
|
||||
controller: configFieldController,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
_configEmpty = configFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
focusNode: configFocusNode,
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Enter config",
|
||||
configFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: _configEmpty
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!_configEmpty
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears The Config Field.",
|
||||
key: const Key("frConfigClearButtonKey"),
|
||||
onTap: () {
|
||||
configFieldController.text = "";
|
||||
|
||||
setState(() {
|
||||
_configEmpty = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From Clipboard To Config Field Input.",
|
||||
key: const Key("frConfigPasteButtonKey"),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
configFieldController.text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_configEmpty =
|
||||
configFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: _configEmpty
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
if (_configEmpty)
|
||||
TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
||||
key: const Key("frConfigScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult = await BarcodeScanner.scan();
|
||||
|
||||
configFieldController.text =
|
||||
qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
_configEmpty =
|
||||
configFieldController.text.isEmpty;
|
||||
});
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const QrCodeIcon(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Start key generation",
|
||||
enabled: !_nameEmpty && !_configEmpty,
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
final config = configFieldController.text;
|
||||
|
||||
if (!Frost.validateEncodedMultisigConfig(
|
||||
encodedConfig: config)) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Invalid config",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!Frost.getParticipants(multisigConfig: config)
|
||||
.contains(myNameFieldController.text)) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "My name not found in config participants",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ref.read(pFrostMyName.state).state = myNameFieldController.text;
|
||||
ref.read(pFrostMultisigConfig.notifier).state = config;
|
||||
|
||||
ref.read(pFrostStartKeyGenData.state).state =
|
||||
Frost.startKeyGeneration(
|
||||
multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
|
||||
myName: ref.read(pFrostMyName.state).state!,
|
||||
);
|
||||
|
||||
ref.read(pFrostCreateNewArgs.state).state = (
|
||||
(
|
||||
walletName: widget.walletName,
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,16 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/import_new_frost_ms_wallet_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/settings_views/wallet_settings_view/frost_ms/resharing/new/new_import_resharer_config_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -17,7 +25,7 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|||
import 'package:stackwallet/widgets/dialogs/simple_mobile_dialog.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class SelectNewFrostImportTypeView extends StatefulWidget {
|
||||
class SelectNewFrostImportTypeView extends ConsumerStatefulWidget {
|
||||
const SelectNewFrostImportTypeView({
|
||||
super.key,
|
||||
required this.walletName,
|
||||
|
@ -30,12 +38,12 @@ class SelectNewFrostImportTypeView extends StatefulWidget {
|
|||
final FrostCurrency frostCurrency;
|
||||
|
||||
@override
|
||||
State<SelectNewFrostImportTypeView> createState() =>
|
||||
ConsumerState<SelectNewFrostImportTypeView> createState() =>
|
||||
_SelectNewFrostImportTypeViewState();
|
||||
}
|
||||
|
||||
class _SelectNewFrostImportTypeViewState
|
||||
extends State<SelectNewFrostImportTypeView> {
|
||||
extends ConsumerState<SelectNewFrostImportTypeView> {
|
||||
_ImportOption _selectedOption = _ImportOption.multisigNew;
|
||||
|
||||
@override
|
||||
|
@ -133,18 +141,60 @@ class _SelectNewFrostImportTypeViewState
|
|||
final String route;
|
||||
switch (_selectedOption) {
|
||||
case _ImportOption.multisigNew:
|
||||
route = ImportNewFrostMsWalletView.routeName;
|
||||
case _ImportOption.resharerExisting:
|
||||
route = NewImportResharerConfigView.routeName;
|
||||
}
|
||||
ref.read(pFrostCreateNewArgs.state).state = (
|
||||
(
|
||||
walletName: widget.walletName,
|
||||
frostCurrency: widget.frostCurrency,
|
||||
),
|
||||
FrostRouteGenerator.importNewConfigStepRoutes,
|
||||
() {
|
||||
// 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
route,
|
||||
arguments: (
|
||||
walletName: widget.walletName,
|
||||
frostCurrency: widget.frostCurrency,
|
||||
),
|
||||
);
|
||||
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,
|
||||
);
|
||||
break;
|
||||
|
||||
case _ImportOption.resharerExisting:
|
||||
await Navigator.of(context).pushNamed(
|
||||
NewImportResharerConfigView.routeName,
|
||||
arguments: (
|
||||
walletName: widget.walletName,
|
||||
frostCurrency: widget.frostCurrency,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
|
@ -21,7 +21,7 @@ import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
|||
class FrostCreateStep1a extends ConsumerStatefulWidget {
|
||||
const FrostCreateStep1a({super.key});
|
||||
|
||||
static const String routeName = "/frostCreateStep1";
|
||||
static const String routeName = "/frostCreateStep1a";
|
||||
static const String title = "Multisig group info";
|
||||
|
||||
@override
|
|
@ -0,0 +1,368 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_route_generator.dart';
|
||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import 'package:stackwallet/services/frost.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/desktop/primary_button.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 FrostCreateStep1b extends ConsumerStatefulWidget {
|
||||
const FrostCreateStep1b({super.key});
|
||||
|
||||
static const String routeName = "/frostCreateStep1b";
|
||||
static const String title = "Import group info";
|
||||
|
||||
@override
|
||||
ConsumerState<FrostCreateStep1b> createState() => _FrostCreateStep1bState();
|
||||
}
|
||||
|
||||
class _FrostCreateStep1bState extends ConsumerState<FrostCreateStep1b> {
|
||||
static const info = [
|
||||
"Scan the config QR code or paste the code provided by the group creator.",
|
||||
"Enter your name EXACTLY as the group creator entered it. When in doubt, "
|
||||
"double check with them. The names are case-sensitive.",
|
||||
"Wait for other participants to finish entering their information.",
|
||||
"Verify that everyone has filled out their forms before continuing. If you "
|
||||
"try to continue before everyone is ready, the process will be canceled.",
|
||||
"Check the box and press “Generate keys”.",
|
||||
];
|
||||
|
||||
late final TextEditingController myNameFieldController, configFieldController;
|
||||
late final FocusNode myNameFocusNode, configFocusNode;
|
||||
|
||||
bool _nameEmpty = true, _configEmpty = true, _userVerifyContinue = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
myNameFieldController = TextEditingController();
|
||||
configFieldController = TextEditingController();
|
||||
myNameFocusNode = FocusNode();
|
||||
configFocusNode = FocusNode();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
myNameFieldController.dispose();
|
||||
configFieldController.dispose();
|
||||
myNameFocusNode.dispose();
|
||||
configFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
const FrostStepUserSteps(
|
||||
userSteps: info,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("frMyNameTextFieldKey"),
|
||||
controller: myNameFieldController,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
_nameEmpty = myNameFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
focusNode: myNameFocusNode,
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"My name",
|
||||
myNameFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: _nameEmpty
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!_nameEmpty
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears The Config Field.",
|
||||
key: const Key("frMyNameClearButtonKey"),
|
||||
onTap: () {
|
||||
myNameFieldController.text = "";
|
||||
|
||||
setState(() {
|
||||
_nameEmpty = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From Clipboard To Name Field.",
|
||||
key: const Key("frMyNamePasteButtonKey"),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
myNameFieldController.text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_nameEmpty =
|
||||
myNameFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: _nameEmpty
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("frConfigTextFieldKey"),
|
||||
controller: configFieldController,
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
_configEmpty = configFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
focusNode: configFocusNode,
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Enter config",
|
||||
configFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: _configEmpty
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!_configEmpty
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears The Config Field.",
|
||||
key: const Key("frConfigClearButtonKey"),
|
||||
onTap: () {
|
||||
configFieldController.text = "";
|
||||
|
||||
setState(() {
|
||||
_configEmpty = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From Clipboard To Config Field Input.",
|
||||
key: const Key("frConfigPasteButtonKey"),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
configFieldController.text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_configEmpty =
|
||||
configFieldController.text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: _configEmpty
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
if (_configEmpty)
|
||||
TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
||||
key: const Key("frConfigScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult = await BarcodeScanner.scan();
|
||||
|
||||
configFieldController.text =
|
||||
qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
_configEmpty =
|
||||
configFieldController.text.isEmpty;
|
||||
});
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const QrCodeIcon(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
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(
|
||||
label: "Start key generation",
|
||||
enabled: _userVerifyContinue && !_nameEmpty && !_configEmpty,
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
final config = configFieldController.text;
|
||||
|
||||
if (!Frost.validateEncodedMultisigConfig(encodedConfig: config)) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Invalid config",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!Frost.getParticipants(multisigConfig: config)
|
||||
.contains(myNameFieldController.text)) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "My name not found in config participants",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ref.read(pFrostMyName.state).state = myNameFieldController.text;
|
||||
ref.read(pFrostMultisigConfig.notifier).state = config;
|
||||
|
||||
ref.read(pFrostStartKeyGenData.state).state =
|
||||
Frost.startKeyGeneration(
|
||||
multisigConfig: ref.read(pFrostMultisigConfig.state).state!,
|
||||
myName: ref.read(pFrostMyName.state).state!,
|
||||
);
|
||||
ref.read(pFrostCreateCurrentStep.state).state = 2;
|
||||
Navigator.of(context).pushNamed(
|
||||
FrostCreateStep2.routeName,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
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_1a.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_1b.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_2.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_3.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/steps/frost_create_step_4.dart';
|
||||
|
@ -30,6 +31,14 @@ abstract class FrostRouteGenerator {
|
|||
(routeName: FrostCreateStep5.routeName, title: FrostCreateStep5.title),
|
||||
];
|
||||
|
||||
static const List<FrostStepRoute> importNewConfigStepRoutes = [
|
||||
(routeName: FrostCreateStep1b.routeName, title: FrostCreateStep1b.title),
|
||||
(routeName: FrostCreateStep2.routeName, title: FrostCreateStep2.title),
|
||||
(routeName: FrostCreateStep3.routeName, title: FrostCreateStep3.title),
|
||||
(routeName: FrostCreateStep4.routeName, title: FrostCreateStep4.title),
|
||||
(routeName: FrostCreateStep5.routeName, title: FrostCreateStep5.title),
|
||||
];
|
||||
|
||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
||||
final args = settings.arguments;
|
||||
|
||||
|
@ -41,6 +50,13 @@ abstract class FrostRouteGenerator {
|
|||
settings: settings,
|
||||
);
|
||||
|
||||
case FrostCreateStep1b.routeName:
|
||||
return RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => const FrostCreateStep1b(),
|
||||
settings: settings,
|
||||
);
|
||||
|
||||
case FrostCreateStep2.routeName:
|
||||
return RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
|
|
|
@ -28,7 +28,6 @@ import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_vi
|
|||
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/frost_scaffold.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/create_new_frost_ms_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/import_new_frost_ms_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/new/select_new_frost_import_type_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/frost_ms/restore/restore_frost_ms_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart';
|
||||
|
@ -500,24 +499,6 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case ImportNewFrostMsWalletView.routeName:
|
||||
if (args is ({
|
||||
String walletName,
|
||||
FrostCurrency frostCurrency,
|
||||
})) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ImportNewFrostMsWalletView(
|
||||
walletName: args.walletName,
|
||||
frostCurrency: args.frostCurrency,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case NewImportResharerConfigView.routeName:
|
||||
if (args is ({
|
||||
String walletName,
|
||||
|
|
Loading…
Reference in a new issue