mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-08 03:39:23 +00:00
feat: overhaul musig setup view iaw frost ui standards
This commit is contained in:
parent
7b629e81b4
commit
d27aad2ad9
1 changed files with 413 additions and 292 deletions
|
@ -1,18 +1,19 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data' show Uint8List;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:nfc_manager/nfc_manager.dart';
|
|
||||||
|
|
||||||
import '../../../../themes/stack_colors.dart';
|
import '../../../../themes/stack_colors.dart';
|
||||||
import '../../../../utilities/assets.dart';
|
import '../../../../utilities/assets.dart';
|
||||||
import '../../../../utilities/text_styles.dart';
|
import '../../../../utilities/text_styles.dart';
|
||||||
import '../../../../utilities/util.dart';
|
import '../../../../utilities/util.dart';
|
||||||
import '../../../../widgets/background.dart';
|
import '../../../../widgets/background.dart';
|
||||||
import '../../../../widgets/custom_buttons/app_bar_icondart';
|
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||||
|
import '../../../widgets/custom_buttons/blue_text_button.dart';
|
||||||
|
import '../../../widgets/desktop/primary_button.dart';
|
||||||
|
import '../../../widgets/dialogs/simple_mobile_dialog.dart';
|
||||||
import '../../../widgets/stack_dialog.dart';
|
import '../../../widgets/stack_dialog.dart';
|
||||||
|
|
||||||
final multisigSetupStateProvider =
|
final multisigSetupStateProvider =
|
||||||
|
@ -118,81 +119,81 @@ class MultisigSetupView extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MultisigSetupViewState extends ConsumerState<MultisigSetupView> {
|
class _MultisigSetupViewState extends ConsumerState<MultisigSetupView> {
|
||||||
bool _isNfcAvailable = false;
|
// bool _isNfcAvailable = false;
|
||||||
String _nfcStatus = 'Checking NFC availability...';
|
// String _nfcStatus = 'Checking NFC availability...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_checkNfcAvailability();
|
// _checkNfcAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkNfcAvailability() async {
|
// Future<void> _checkNfcAvailability() async {
|
||||||
try {
|
// try {
|
||||||
final availability = await NfcManager.instance.isAvailable();
|
// final availability = await NfcManager.instance.isAvailable();
|
||||||
setState(() {
|
// setState(() {
|
||||||
_isNfcAvailable = availability;
|
// _isNfcAvailable = availability;
|
||||||
_nfcStatus = _isNfcAvailable
|
// _nfcStatus = _isNfcAvailable
|
||||||
? 'NFC is available'
|
// ? 'NFC is available'
|
||||||
: 'NFC is not available on this device';
|
// : 'NFC is not available on this device';
|
||||||
});
|
// });
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
setState(() {
|
// setState(() {
|
||||||
_nfcStatus = 'Error checking NFC: $e';
|
// _nfcStatus = 'Error checking NFC: $e';
|
||||||
_isNfcAvailable = false;
|
// _isNfcAvailable = false;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Future<void> _startNfcSession() async {
|
// Future<void> _startNfcSession() async {
|
||||||
if (!_isNfcAvailable) return;
|
// if (!_isNfcAvailable) return;
|
||||||
|
//
|
||||||
setState(() => _nfcStatus = 'Ready to exchange information...');
|
// setState(() => _nfcStatus = 'Ready to exchange information...');
|
||||||
|
//
|
||||||
try {
|
// try {
|
||||||
await NfcManager.instance.startSession(
|
// await NfcManager.instance.startSession(
|
||||||
onDiscovered: (tag) async {
|
// onDiscovered: (tag) async {
|
||||||
try {
|
// try {
|
||||||
final ndef = Ndef.from(tag);
|
// final ndef = Ndef.from(tag);
|
||||||
|
//
|
||||||
if (ndef == null) {
|
// if (ndef == null) {
|
||||||
setState(() => _nfcStatus = 'Tag is not NDEF compatible');
|
// setState(() => _nfcStatus = 'Tag is not NDEF compatible');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
final setupData = ref.watch(multisigSetupStateProvider);
|
// final setupData = ref.watch(multisigSetupStateProvider);
|
||||||
|
//
|
||||||
if (ndef.isWritable) {
|
// if (ndef.isWritable) {
|
||||||
final message = NdefMessage([
|
// final message = NdefMessage([
|
||||||
NdefRecord.createMime(
|
// NdefRecord.createMime(
|
||||||
'application/x-multisig-setup',
|
// 'application/x-multisig-setup',
|
||||||
Uint8List.fromList(
|
// Uint8List.fromList(
|
||||||
utf8.encode(jsonEncode(setupData.toJson()))),
|
// utf8.encode(jsonEncode(setupData.toJson()))),
|
||||||
),
|
// ),
|
||||||
]);
|
// ]);
|
||||||
|
//
|
||||||
try {
|
// try {
|
||||||
await ndef.write(message);
|
// await ndef.write(message);
|
||||||
setState(
|
// setState(
|
||||||
() => _nfcStatus = 'Configuration shared successfully');
|
// () => _nfcStatus = 'Configuration shared successfully');
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
setState(
|
// setState(
|
||||||
() => _nfcStatus = 'Failed to share configuration: $e');
|
// () => _nfcStatus = 'Failed to share configuration: $e');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
await NfcManager.instance.stopSession();
|
// await NfcManager.instance.stopSession();
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
setState(() => _nfcStatus = 'Error during NFC exchange: $e');
|
// setState(() => _nfcStatus = 'Error during NFC exchange: $e');
|
||||||
await NfcManager.instance.stopSession();
|
// await NfcManager.instance.stopSession();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
setState(() => _nfcStatus = 'Error: $e');
|
// setState(() => _nfcStatus = 'Error: $e');
|
||||||
await NfcManager.instance.stopSession();
|
// await NfcManager.instance.stopSession();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Displays a short explanation dialog about musig.
|
/// Displays a short explanation dialog about musig.
|
||||||
Future<void> _showMultisigInfoDialog() async {
|
Future<void> _showMultisigInfoDialog() async {
|
||||||
|
@ -214,15 +215,180 @@ class _MultisigSetupViewState extends ConsumerState<MultisigSetupView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showScriptTypeDialog() {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => SimpleMobileDialog(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"What is a script type?",
|
||||||
|
style: STextStyles.w600_20(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"The script type you choose determines the type of wallet "
|
||||||
|
"addresses and the size and structure of transactions.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Legacy (P2SH):",
|
||||||
|
style: STextStyles.w600_18(context),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"The original multisig format. Compatible with all wallets but has "
|
||||||
|
"higher transaction fees. P2SH addresses begin with 3.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Nested SegWit (P2SH-P2WSH):",
|
||||||
|
style: STextStyles.w600_18(context),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"A newer format that reduces transaction fees while maintaining "
|
||||||
|
"broad compatibility. P2SH-P2WSH addresses begin with 3.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Native SegWit (P2WSH):",
|
||||||
|
style: STextStyles.w600_18(context),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"The lowest transaction fees, but may not be compatible with older "
|
||||||
|
"wallets. P2WSH addresses begin with bc1.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _thresholdController = TextEditingController();
|
||||||
|
final _participantsController = TextEditingController();
|
||||||
|
|
||||||
|
final List<TextEditingController> controllers = [];
|
||||||
|
|
||||||
|
int _participantsCount = 0;
|
||||||
|
|
||||||
|
String _validateInputData() {
|
||||||
|
final threshold = int.tryParse(_thresholdController.text);
|
||||||
|
if (threshold == null) {
|
||||||
|
return "Choose a threshold";
|
||||||
|
}
|
||||||
|
|
||||||
|
final partsCount = int.tryParse(_participantsController.text);
|
||||||
|
if (partsCount == null) {
|
||||||
|
return "Choose total number of participants";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threshold > partsCount) {
|
||||||
|
return "Threshold cannot be greater than the number of participants";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partsCount < 2) {
|
||||||
|
return "At least two participants required";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controllers.length != partsCount) {
|
||||||
|
return "Participants count error";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "valid";
|
||||||
|
}
|
||||||
|
|
||||||
|
void _participantsCountChanged(String newValue) {
|
||||||
|
final count = int.tryParse(newValue);
|
||||||
|
if (count != null) {
|
||||||
|
if (count > _participantsCount) {
|
||||||
|
for (int i = _participantsCount; i < count; i++) {
|
||||||
|
controllers.add(TextEditingController());
|
||||||
|
}
|
||||||
|
|
||||||
|
_participantsCount = count;
|
||||||
|
setState(() {});
|
||||||
|
} else if (count < _participantsCount) {
|
||||||
|
for (int i = _participantsCount; i > count; i--) {
|
||||||
|
final last = controllers.removeLast();
|
||||||
|
last.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_participantsCount = count;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showWhatIsThresholdDialog() {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => SimpleMobileDialog(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"What is a threshold?",
|
||||||
|
style: STextStyles.w600_20(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"A threshold is the amount of people required to perform an "
|
||||||
|
"action. This does not have to be the same number as the "
|
||||||
|
"total number in the group.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"For example, if you have 3 people in the group, but a threshold "
|
||||||
|
"of 2, then you only need 2 out of the 3 people to sign for an "
|
||||||
|
"action to take place.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 6,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Conversely if you have a group of 3 AND a threshold of 3, you "
|
||||||
|
"will need all 3 people in the group to sign to approve any "
|
||||||
|
"action.",
|
||||||
|
style: STextStyles.w400_16(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_thresholdController.dispose();
|
||||||
|
_participantsController.dispose();
|
||||||
|
for (final controller in controllers) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool isDesktop = Util.isDesktop;
|
|
||||||
final setupData = ref.watch(multisigSetupStateProvider);
|
final setupData = ref.watch(multisigSetupStateProvider);
|
||||||
|
final bool isDesktop = Util.isDesktop;
|
||||||
// Required signatures<= total cosigners.
|
|
||||||
final clampedThreshold = (setupData.threshold > setupData.totalCosigners)
|
|
||||||
? setupData.totalCosigners
|
|
||||||
: setupData.threshold;
|
|
||||||
|
|
||||||
return Background(
|
return Background(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
@ -265,227 +431,182 @@ class _MultisigSetupViewState extends ConsumerState<MultisigSetupView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: LayoutBuilder(
|
body: SingleChildScrollView(
|
||||||
builder: (builderContext, constraints) {
|
padding: const EdgeInsets.all(16),
|
||||||
return SingleChildScrollView(
|
child: Column(
|
||||||
child: ConstrainedBox(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
constraints: BoxConstraints(
|
mainAxisSize: MainAxisSize.min,
|
||||||
minHeight: constraints.maxHeight,
|
children: [
|
||||||
),
|
Text(
|
||||||
child: IntrinsicHeight(
|
"Configuration",
|
||||||
child: Padding(
|
style: STextStyles.itemSubtitle(context),
|
||||||
padding: const EdgeInsets.all(16),
|
),
|
||||||
child: Column(
|
const SizedBox(height: 16),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// We'll add a method to share the config w/ cosigners
|
|
||||||
// so there's not much need to remind them here.
|
|
||||||
// RoundedWhiteContainer(
|
|
||||||
// child: Text(
|
|
||||||
// "Make sure all cosigners use the same "
|
|
||||||
// "configuration when creating the shared account.",
|
|
||||||
// style: STextStyles.w500_12(context).copyWith(
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .textSubtitle1,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 16),
|
|
||||||
|
|
||||||
Text(
|
// Script type.
|
||||||
"Configuration",
|
Column(
|
||||||
style: STextStyles.itemSubtitle(context),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: [
|
||||||
const SizedBox(height: 16),
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
// Script Type Selection
|
children: [
|
||||||
RoundedContainer(
|
Text(
|
||||||
padding: const EdgeInsets.all(16),
|
"Script type",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.popupBG,
|
.textDark3,
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Script Type",
|
|
||||||
style: STextStyles.titleBold12(context),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
DropdownButtonFormField<MultisigScriptType>(
|
|
||||||
value: setupData.scriptType,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
items: MultisigScriptType.values.map((type) {
|
|
||||||
String label;
|
|
||||||
switch (type) {
|
|
||||||
case MultisigScriptType.legacy:
|
|
||||||
label = "Legacy (P2SH)";
|
|
||||||
break;
|
|
||||||
case MultisigScriptType.segwit:
|
|
||||||
label = "Nested SegWit (P2SH-P2WSH)";
|
|
||||||
break;
|
|
||||||
case MultisigScriptType.nativeSegwit:
|
|
||||||
label = "Native SegWit (P2WSH)";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: type,
|
|
||||||
child: Text(label),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
ref
|
|
||||||
.read(
|
|
||||||
multisigSetupStateProvider.notifier,
|
|
||||||
)
|
|
||||||
.updateScriptType(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
text: "What is a script type?",
|
||||||
|
onTap: _showScriptTypeDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
DropdownButtonFormField<MultisigScriptType>(
|
||||||
|
value: setupData.scriptType,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: MultisigScriptType.values.map((type) {
|
||||||
|
String label;
|
||||||
|
switch (type) {
|
||||||
|
case MultisigScriptType.legacy:
|
||||||
|
label = "Legacy (P2SH)";
|
||||||
|
break;
|
||||||
|
case MultisigScriptType.segwit:
|
||||||
|
label = "Nested SegWit (P2SH-P2WSH)";
|
||||||
|
break;
|
||||||
|
case MultisigScriptType.nativeSegwit:
|
||||||
|
label = "Native SegWit (P2WSH)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: type,
|
||||||
|
child: Text(label),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref
|
||||||
|
.read(multisigSetupStateProvider.notifier)
|
||||||
|
.updateScriptType(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
// Threshold and Participants.
|
||||||
|
Column(
|
||||||
// Multisig params setup
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
RoundedContainer(
|
Text(
|
||||||
padding: const EdgeInsets.all(16),
|
"Number of participants",
|
||||||
color: Theme.of(context)
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
.extension<StackColors>()!
|
color: Theme.of(context)
|
||||||
.popupBG,
|
.extension<StackColors>()!
|
||||||
child: Column(
|
.textDark3,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"Total cosigners: ${setupData.totalCosigners}",
|
|
||||||
style: STextStyles.titleBold12(context),
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
value: setupData.totalCosigners.toDouble(),
|
|
||||||
min: 2,
|
|
||||||
max: 7, // There's not actually a max.
|
|
||||||
divisions: 7, // Match the above or look off.
|
|
||||||
label:
|
|
||||||
"${setupData.totalCosigners} cosigners",
|
|
||||||
onChanged: (value) {
|
|
||||||
ref
|
|
||||||
.read(
|
|
||||||
multisigSetupStateProvider.notifier)
|
|
||||||
.updateTotalCosigners(value.toInt());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
"Required Signatures: $clampedThreshold of ${setupData.totalCosigners}",
|
|
||||||
style: STextStyles.titleBold12(context),
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
value: clampedThreshold.toDouble(),
|
|
||||||
min: 1,
|
|
||||||
max: setupData.totalCosigners.toDouble(),
|
|
||||||
divisions: setupData.totalCosigners - 1,
|
|
||||||
label:
|
|
||||||
"$clampedThreshold of ${setupData.totalCosigners}",
|
|
||||||
onChanged: (value) {
|
|
||||||
ref
|
|
||||||
.read(
|
|
||||||
multisigSetupStateProvider.notifier)
|
|
||||||
.updateThreshold(value.toInt());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// We'll make a FROST-like progress indicator in a
|
|
||||||
// dialog to show the progress of the setup process.
|
|
||||||
// This simpler example will be removed soon.
|
|
||||||
// Text(
|
|
||||||
// "Exchange Method",
|
|
||||||
// style: STextStyles.itemSubtitle(context),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 16),
|
|
||||||
//
|
|
||||||
// // NFC exchange.
|
|
||||||
// RoundedContainer(
|
|
||||||
// padding: const EdgeInsets.all(16),
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .popupBG,
|
|
||||||
// child: Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// children: [
|
|
||||||
// Row(
|
|
||||||
// children: [
|
|
||||||
// Icon(
|
|
||||||
// _isNfcAvailable
|
|
||||||
// ? Icons.nfc
|
|
||||||
// : Icons.nfc_outlined,
|
|
||||||
// color: _isNfcAvailable
|
|
||||||
// ? Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .accentColorGreen
|
|
||||||
// : Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .textDark3,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 8),
|
|
||||||
// Text(
|
|
||||||
// "NFC Exchange",
|
|
||||||
// style: STextStyles.titleBold12(context),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 16),
|
|
||||||
// Text(
|
|
||||||
// _nfcStatus,
|
|
||||||
// style: STextStyles.baseXS(context),
|
|
||||||
// ),
|
|
||||||
// if (_isNfcAvailable) ...[
|
|
||||||
// const SizedBox(height: 16),
|
|
||||||
// SizedBox(
|
|
||||||
// width: double.infinity,
|
|
||||||
// child: !isDesktop
|
|
||||||
// ? TextButton(
|
|
||||||
// onPressed: _startNfcSession,
|
|
||||||
// style: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .getPrimaryEnabledButtonStyle(
|
|
||||||
// context),
|
|
||||||
// child: Text(
|
|
||||||
// "Tap to Exchange Information",
|
|
||||||
// style:
|
|
||||||
// STextStyles.button(context),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// : PrimaryButton(
|
|
||||||
// label:
|
|
||||||
// "Tap to Exchange Information",
|
|
||||||
// onPressed: _startNfcSession,
|
|
||||||
// enabled: true,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
controller: _participantsController,
|
||||||
|
onChanged: _participantsCountChanged,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter number of participants",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Threshold",
|
||||||
|
style: STextStyles.w500_14(context).copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomTextButton(
|
||||||
|
text: "What is a threshold?",
|
||||||
|
onTap: _showWhatIsThresholdDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
controller: _thresholdController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Enter number of signatures",
|
||||||
|
hintStyle: STextStyles.fieldLabel(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 24),
|
||||||
},
|
|
||||||
|
// TODO: Push button to bottom of page.
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Create multisignature account",
|
||||||
|
onPressed: () async {
|
||||||
|
if (FocusScope.of(context).hasFocus) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
final validationMessage = _validateInputData();
|
||||||
|
|
||||||
|
if (validationMessage != "valid") {
|
||||||
|
return await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => StackOkDialog(
|
||||||
|
title: validationMessage,
|
||||||
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Adapt the FROST config steps UI.
|
||||||
|
// final config = Frost.createMultisigConfig(
|
||||||
|
// name: controllers.first.text.trim(),
|
||||||
|
// threshold: int.parse(_thresholdController.text),
|
||||||
|
// participants:
|
||||||
|
// controllers.map((e) => e.text.trim()).toList(),
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// ref.read(pFrostMyName.notifier).state =
|
||||||
|
// controllers.first.text.trim();
|
||||||
|
// ref.read(pFrostMultisigConfig.notifier).state = config;
|
||||||
|
//
|
||||||
|
// ref.read(pFrostScaffoldArgs.state).state = (
|
||||||
|
// info: (
|
||||||
|
// walletName: widget.walletName,
|
||||||
|
// frostCurrency: widget.frostCurrency,
|
||||||
|
// ),
|
||||||
|
// walletId: null,
|
||||||
|
// stepRoutes: FrostRouteGenerator.createNewConfigStepRoutes,
|
||||||
|
// frostInterruptionDialogType:
|
||||||
|
// FrostInterruptionDialogType.walletCreation,
|
||||||
|
// parentNav: Navigator.of(context),
|
||||||
|
// callerRouteName: CreateNewFrostMsWalletView.routeName,
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// await Navigator.of(context).pushNamed(
|
||||||
|
// FrostStepScaffold.routeName,
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue