mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
frost transaction sending ui update
This commit is contained in:
parent
1be51a666b
commit
832be89cab
6 changed files with 377 additions and 362 deletions
|
@ -82,6 +82,9 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
) as BitcoinFrostWallet;
|
||||
|
||||
_threshold = wallet.frostInfo.threshold;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(pFrostMyName.state).state = wallet.frostInfo.myName;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -116,7 +119,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
TextSpan(
|
||||
text:
|
||||
"Share this config with the group members. ",
|
||||
style: STextStyles.w600_12(context),
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
|
@ -159,7 +162,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 20 : 16,
|
||||
height: Util.isDesktop ? 20 : 12,
|
||||
),
|
||||
SizedBox(
|
||||
height: qrImageSize,
|
||||
|
@ -179,7 +182,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 20 : 16,
|
||||
height: Util.isDesktop ? 20 : 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Encoded transaction config",
|
||||
|
@ -193,7 +196,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 20 : 16,
|
||||
height: Util.isDesktop ? 20 : 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Threshold",
|
||||
|
@ -201,7 +204,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
horizontal: true,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 20 : 16,
|
||||
height: Util.isDesktop ? 20 : 12,
|
||||
),
|
||||
if (!Util.isDesktop)
|
||||
const Spacer(
|
||||
|
@ -217,7 +220,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
|||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 20 : 16,
|
||||
height: Util.isDesktop ? 20 : 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Attempt sign",
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
@ -41,7 +42,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
|||
late final TextEditingController configFieldController;
|
||||
late final FocusNode configFocusNode;
|
||||
|
||||
bool _configEmpty = true;
|
||||
bool _configEmpty = true, _userVerifyContinue = false;
|
||||
|
||||
bool _attemptSignLock = false;
|
||||
|
||||
|
@ -125,6 +126,12 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
|||
void initState() {
|
||||
configFieldController = TextEditingController();
|
||||
configFocusNode = FocusNode();
|
||||
final wallet = ref.read(pWallets).getWallet(
|
||||
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||
) as BitcoinFrostWallet;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(pFrostMyName.state).state = wallet.frostInfo.myName;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -146,7 +153,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
|||
const FrostStepUserSteps(
|
||||
userSteps: info,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 20),
|
||||
FrostStepField(
|
||||
controller: configFieldController,
|
||||
focusNode: configFocusNode,
|
||||
|
@ -159,16 +166,25 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
|||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
height: 12,
|
||||
),
|
||||
CheckboxTextButton(
|
||||
label: "I have verified that everyone has imported he config and"
|
||||
" is ready to sign",
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_userVerifyContinue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Start signing",
|
||||
enabled: !_configEmpty,
|
||||
enabled: !_configEmpty && _userVerifyContinue,
|
||||
onPressed: () {
|
||||
_attemptSign();
|
||||
},
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
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/frost_route_generator.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
|
@ -8,7 +6,6 @@ import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
|||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/frost.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
|
@ -17,13 +14,9 @@ import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.dart';
|
|||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/detail_item.dart';
|
||||
import 'package:stackwallet/widgets/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/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
|
||||
|
||||
class FrostSendStep2 extends ConsumerStatefulWidget {
|
||||
const FrostSendStep2({super.key});
|
||||
|
@ -47,7 +40,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
|
||||
final List<bool> fieldIsEmptyFlags = [];
|
||||
|
||||
bool hasEnoughPreprocesses() {
|
||||
int countPreprocesses() {
|
||||
// own preprocess is not included in controllers and must be set here
|
||||
int count = 1;
|
||||
|
||||
|
@ -57,7 +50,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
}
|
||||
}
|
||||
|
||||
return count >= threshold;
|
||||
return count;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -124,11 +117,14 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"1.",
|
||||
"2.",
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -141,7 +137,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
TextSpan(
|
||||
text:
|
||||
"Enter their preprocesses into the corresponding fields. ",
|
||||
style: STextStyles.w600_12(context),
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
TextSpan(
|
||||
text: "You must have the threshold number of "
|
||||
|
@ -164,9 +160,24 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Threshold",
|
||||
detail: "$threshold signatures",
|
||||
horizontal: true,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "My name",
|
||||
detail: myName,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: myName,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: myName,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
|
@ -189,141 +200,46 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"You need to obtain ${threshold - 1} preprocess from signing members to send this transaction.",
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final count = countPreprocesses();
|
||||
final colors = Theme.of(context).extension<StackColors>()!;
|
||||
return DetailItem(
|
||||
title: "Required preprocesses",
|
||||
detail: "$count of $threshold",
|
||||
horizontal: true,
|
||||
overrideDetailTextColor: count >= threshold
|
||||
? colors.accentColorGreen
|
||||
: colors.accentColorRed,
|
||||
);
|
||||
}),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: Key("frostPreprocessesTextFieldKey_$i"),
|
||||
controller: controllers[i],
|
||||
focusNode: focusNodes[i],
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] =
|
||||
controllers[i].text.isEmpty;
|
||||
});
|
||||
},
|
||||
decoration: standardInputDecoration(
|
||||
"Enter ${participantsWithoutMe[i]}'s preprocess",
|
||||
focusNodes[i],
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: fieldIsEmptyFlags[i]
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!fieldIsEmptyFlags[i]
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears The Preprocess Field Input.",
|
||||
key: Key(
|
||||
"frostPreprocessesClearButtonKey_$i",
|
||||
),
|
||||
onTap: () {
|
||||
controllers[i].text = "";
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From Clipboard To Preprocess Field Input.",
|
||||
key: Key(
|
||||
"frostPreprocessesPasteButtonKey_$i",
|
||||
),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
controllers[i].text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] =
|
||||
controllers[i].text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: fieldIsEmptyFlags[i]
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
if (fieldIsEmptyFlags[i])
|
||||
TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Scan QR Button. Opens Camera For Scanning QR Code.",
|
||||
key: Key(
|
||||
"frostPreprocessesScanQrButtonKey_$i",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context)
|
||||
.hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult =
|
||||
await BarcodeScanner.scan();
|
||||
|
||||
controllers[i].text =
|
||||
qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] =
|
||||
controllers[i].text.isEmpty;
|
||||
});
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const QrCodeIcon(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
FrostStepField(
|
||||
label: participantsWithoutMe[i],
|
||||
hint: "Enter ${participantsWithoutMe[i]}'s preprocess",
|
||||
controller: controllers[i],
|
||||
focusNode: focusNodes[i],
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||
});
|
||||
},
|
||||
showQrScanOption: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -332,8 +248,8 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
|||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Continue signing",
|
||||
enabled: hasEnoughPreprocesses(),
|
||||
label: "Generate shares",
|
||||
enabled: countPreprocesses() >= threshold,
|
||||
onPressed: () async {
|
||||
// collect Preprocess strings (not including my own)
|
||||
final preprocesses = controllers.map((e) => e.text).toList();
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
import 'package:barcode_scan2/barcode_scan2.dart';
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as cl;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/frost_route_generator.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/frost.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/detail_item.dart';
|
||||
import 'package:stackwallet/widgets/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/frost_step_user_steps.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
|
||||
|
||||
class FrostSendStep3 extends ConsumerStatefulWidget {
|
||||
const FrostSendStep3({super.key});
|
||||
|
@ -34,6 +30,11 @@ class FrostSendStep3 extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||
static const info = [
|
||||
"Send your share to other signing group members.",
|
||||
"Enter their shares into the corresponding fields.",
|
||||
];
|
||||
|
||||
final List<TextEditingController> controllers = [];
|
||||
final List<FocusNode> focusNodes = [];
|
||||
|
||||
|
@ -45,6 +46,8 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
|
||||
final List<bool> fieldIsEmptyFlags = [];
|
||||
|
||||
bool _userVerifyContinue = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final wallet = ref.read(pWallets).getWallet(
|
||||
|
@ -93,15 +96,28 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DetailItem(
|
||||
title: "My name",
|
||||
detail: myName,
|
||||
const FrostStepUserSteps(
|
||||
userSteps: info,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "My shares",
|
||||
title: "My name",
|
||||
detail: myName,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: myName,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: myName,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "My share",
|
||||
detail: myShare,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
|
@ -125,133 +141,17 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: Key("frostSharesTextFieldKey_$i"),
|
||||
controller: controllers[i],
|
||||
focusNode: focusNodes[i],
|
||||
readOnly: false,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Enter ${participantsWithoutMe[i]}'s share",
|
||||
focusNodes[i],
|
||||
context,
|
||||
).copyWith(
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 6,
|
||||
bottom: 8,
|
||||
right: 5,
|
||||
),
|
||||
suffixIcon: Padding(
|
||||
padding: fieldIsEmptyFlags[i]
|
||||
? const EdgeInsets.only(right: 8)
|
||||
: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
!fieldIsEmptyFlags[i]
|
||||
? TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Clear Button. Clears "
|
||||
"The Share Field Input.",
|
||||
key: Key(
|
||||
"frostSharesClearButtonKey_$i",
|
||||
),
|
||||
onTap: () {
|
||||
controllers[i].text = "";
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] = true;
|
||||
});
|
||||
},
|
||||
child: const XIcon(),
|
||||
)
|
||||
: TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Paste Button. Pastes From "
|
||||
"Clipboard To Share Field Input.",
|
||||
key: Key(
|
||||
"frostSharesPasteButtonKey_$i"),
|
||||
onTap: () async {
|
||||
final ClipboardData? data =
|
||||
await Clipboard.getData(
|
||||
Clipboard.kTextPlain);
|
||||
if (data?.text != null &&
|
||||
data!.text!.isNotEmpty) {
|
||||
controllers[i].text =
|
||||
data.text!.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] =
|
||||
controllers[i].text.isEmpty;
|
||||
});
|
||||
},
|
||||
child: fieldIsEmptyFlags[i]
|
||||
? const ClipboardIcon()
|
||||
: const XIcon(),
|
||||
),
|
||||
if (fieldIsEmptyFlags[i])
|
||||
TextFieldIconButton(
|
||||
semanticsLabel:
|
||||
"Scan QR Button. Opens Camera "
|
||||
"For Scanning QR Code.",
|
||||
key: Key(
|
||||
"frostSharesScanQrButtonKey_$i",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context)
|
||||
.hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75));
|
||||
}
|
||||
|
||||
final qrResult =
|
||||
await BarcodeScanner.scan();
|
||||
|
||||
controllers[i].text =
|
||||
qrResult.rawContent;
|
||||
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] =
|
||||
controllers[i].text.isEmpty;
|
||||
});
|
||||
} on PlatformException catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions "
|
||||
"while trying to scan qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const QrCodeIcon(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
FrostStepField(
|
||||
label: participantsWithoutMe[i],
|
||||
hint: "Enter ${participantsWithoutMe[i]}'s share",
|
||||
controller: controllers[i],
|
||||
focusNode: focusNodes[i],
|
||||
onChanged: (_) {
|
||||
setState(() {
|
||||
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||
});
|
||||
},
|
||||
showQrScanOption: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -259,22 +159,22 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
CheckboxTextButton(
|
||||
label: "I have verified that everyone has my share",
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_userVerifyContinue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Complete signing",
|
||||
label: "Generate transaction",
|
||||
enabled: _userVerifyContinue &&
|
||||
!fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
||||
onPressed: () async {
|
||||
// check for empty shares
|
||||
if (controllers
|
||||
.map((e) => e.text.isEmpty)
|
||||
.reduce((value, element) => value |= element)) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Missing Shares",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// collect Share strings
|
||||
final sharesCollected = controllers.map((e) => e.text).toList();
|
||||
|
||||
|
@ -295,10 +195,32 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
shares: shares,
|
||||
);
|
||||
|
||||
ref.read(pFrostTxData.state).state =
|
||||
ref.read(pFrostTxData.state).state!.copyWith(
|
||||
raw: rawTx,
|
||||
);
|
||||
final tx = cl.Transaction.fromHex(rawTx);
|
||||
final txData = ref.read(pFrostTxData)!;
|
||||
|
||||
final fractionDigits =
|
||||
txData.recipients!.first.amount.fractionDigits;
|
||||
|
||||
final inputTotal = Amount(
|
||||
rawValue: txData.utxos!
|
||||
.map((e) => BigInt.from(e.value))
|
||||
.reduce((v, e) => v += e),
|
||||
fractionDigits: fractionDigits,
|
||||
);
|
||||
final outputTotal = Amount(
|
||||
rawValue:
|
||||
tx.outputs.map((e) => e.value).reduce((v, e) => v += e),
|
||||
fractionDigits: fractionDigits,
|
||||
);
|
||||
|
||||
ref.read(pFrostTxData.state).state = txData.copyWith(
|
||||
raw: rawTx,
|
||||
fee: inputTotal - outputTotal,
|
||||
frostSigners: [
|
||||
myName,
|
||||
...participantsWithoutMe,
|
||||
],
|
||||
);
|
||||
|
||||
ref.read(pFrostCreateCurrentStep.state).state = 4;
|
||||
await Navigator.of(context).pushNamed(
|
||||
|
@ -313,13 +235,15 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
|||
level: LogLevel.Fatal,
|
||||
);
|
||||
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Failed to complete signing process",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Failed to complete signing process",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/frost_route_generator.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart';
|
||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/detail_item.dart';
|
||||
import 'package:stackwallet/widgets/expandable.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class FrostSendStep4 extends ConsumerStatefulWidget {
|
||||
|
@ -27,46 +35,154 @@ class FrostSendStep4 extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||
final List<bool> _expandedStates = [];
|
||||
|
||||
bool _broadcastLock = false;
|
||||
|
||||
late final CryptoCurrency cryptoCurrency;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final wallet = ref.read(pWallets).getWallet(
|
||||
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||
) as BitcoinFrostWallet;
|
||||
|
||||
cryptoCurrency = wallet.cryptoCurrency;
|
||||
|
||||
for (final _ in ref.read(pFrostTxData)!.recipients!) {
|
||||
_expandedStates.add(false);
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final signerNames = ref.watch(pFrostTxData)!.frostSigners!;
|
||||
final recipients = ref.watch(pFrostTxData)!.recipients!;
|
||||
|
||||
final String signers;
|
||||
if (signerNames.length > 1) {
|
||||
signers = signerNames
|
||||
.sublist(1)
|
||||
.fold(signerNames.first, (pv, e) => pv += ", $e");
|
||||
} else {
|
||||
signers = signerNames.first;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 220,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
QrImageView(
|
||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
||||
size: 220,
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
foregroundColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
],
|
||||
if (kDebugMode)
|
||||
DetailItem(
|
||||
title: "Tx hex (debug mode only)",
|
||||
detail: ref.watch(pFrostTxData)!.raw!,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: ref.watch(pFrostTxData)!.raw!,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: ref.watch(pFrostTxData)!.raw!,
|
||||
),
|
||||
),
|
||||
if (kDebugMode)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Text(
|
||||
"Send ${cryptoCurrency.coin.ticker}",
|
||||
style: STextStyles.w600_20(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
recipients.length == 1
|
||||
? _Recipient(
|
||||
address: recipients[0].address,
|
||||
amount: ref
|
||||
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||
.format(recipients[0].amount),
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
for (int i = 0; i < recipients.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Expandable(
|
||||
onExpandChanged: (state) {
|
||||
setState(() {
|
||||
_expandedStates[i] =
|
||||
state == ExpandableState.expanded;
|
||||
});
|
||||
},
|
||||
header: Padding(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Recipient ${i + 1}",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
_expandedStates[i]
|
||||
? Assets.svg.chevronUp
|
||||
: Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: _Recipient(
|
||||
address: recipients[i].address,
|
||||
amount: ref
|
||||
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||
.format(recipients[i].amount),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Transaction fee",
|
||||
detail: ref
|
||||
.watch(pAmountFormatter(cryptoCurrency.coin))
|
||||
.format(ref.watch(pFrostTxData)!.fee!),
|
||||
horizontal: true,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Raw transaction hex",
|
||||
detail: ref.watch(pFrostTxData.state).state!.raw!,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
||||
),
|
||||
title: "Total",
|
||||
detail: ref.watch(pAmountFormatter(cryptoCurrency.coin)).format(
|
||||
ref.watch(pFrostTxData)!.fee! +
|
||||
recipients.map((e) => e.amount).reduce((v, e) => v += e)),
|
||||
horizontal: true,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Note",
|
||||
detail: ref.watch(pFrostTxData)!.note ?? "",
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Signers",
|
||||
detail: signers,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
|
@ -76,7 +192,7 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
|||
height: 12,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Broadcast Transaction",
|
||||
label: "Approve transaction",
|
||||
onPressed: () async {
|
||||
if (_broadcastLock) {
|
||||
return;
|
||||
|
@ -92,11 +208,11 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
|||
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||
)
|
||||
.confirmSend(
|
||||
txData: ref.read(pFrostTxData.state).state!,
|
||||
txData: ref.read(pFrostTxData)!,
|
||||
),
|
||||
context: context,
|
||||
message: "Broadcasting transaction to network",
|
||||
isDesktop: Util.isDesktop,
|
||||
isDesktop: true, // used to pop using root nav
|
||||
onException: (e) {
|
||||
ex = e;
|
||||
},
|
||||
|
@ -106,7 +222,7 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
|||
throw ex!;
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
if (txData != null) {
|
||||
ref.read(pFrostTxData.state).state = txData;
|
||||
Navigator.of(context).popUntil(
|
||||
|
@ -123,15 +239,18 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
|||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Broadcast error",
|
||||
message: e.toString(),
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Broadcast error",
|
||||
message: e.toString(),
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
onOkPressed:
|
||||
Navigator.of(context, rootNavigator: true).pop,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_broadcastLock = false;
|
||||
}
|
||||
|
@ -142,3 +261,35 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Recipient extends StatelessWidget {
|
||||
const _Recipient({
|
||||
super.key,
|
||||
required this.address,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String address;
|
||||
final String amount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DetailItem(
|
||||
title: "Address",
|
||||
detail: address,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Amount",
|
||||
detail: amount,
|
||||
horizontal: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,9 @@ class TxData {
|
|||
|
||||
final String? changeAddress;
|
||||
|
||||
// frost specific
|
||||
final String? frostMSConfig;
|
||||
final List<String>? frostSigners;
|
||||
|
||||
// paynym specific
|
||||
final PaynymAccountLite? paynymAccountLite;
|
||||
|
@ -91,6 +93,7 @@ class TxData {
|
|||
this.usedUTXOs,
|
||||
this.changeAddress,
|
||||
this.frostMSConfig,
|
||||
this.frostSigners,
|
||||
this.paynymAccountLite,
|
||||
this.web3dartTransaction,
|
||||
this.nonce,
|
||||
|
@ -166,6 +169,7 @@ class TxData {
|
|||
})>?
|
||||
recipients,
|
||||
String? frostMSConfig,
|
||||
List<String>? frostSigners,
|
||||
String? changeAddress,
|
||||
PaynymAccountLite? paynymAccountLite,
|
||||
web3dart.Transaction? web3dartTransaction,
|
||||
|
@ -209,6 +213,7 @@ class TxData {
|
|||
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
||||
recipients: recipients ?? this.recipients,
|
||||
frostMSConfig: frostMSConfig ?? this.frostMSConfig,
|
||||
frostSigners: frostSigners ?? this.frostSigners,
|
||||
changeAddress: changeAddress ?? this.changeAddress,
|
||||
paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite,
|
||||
web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction,
|
||||
|
@ -249,7 +254,7 @@ class TxData {
|
|||
'recipients: $recipients, '
|
||||
'utxos: $utxos, '
|
||||
'usedUTXOs: $usedUTXOs, '
|
||||
'frostMSConfig: $frostMSConfig, '
|
||||
'frostSigners: $frostSigners, '
|
||||
'changeAddress: $changeAddress, '
|
||||
'paynymAccountLite: $paynymAccountLite, '
|
||||
'web3dartTransaction: $web3dartTransaction, '
|
||||
|
|
Loading…
Reference in a new issue