mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-05-03 19:32:40 +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
lib
pages/send_view/frost_ms/send_steps
frost_send_step_1a.dartfrost_send_step_1b.dartfrost_send_step_2.dartfrost_send_step_3.dartfrost_send_step_4.dart
wallets/models
|
@ -82,6 +82,9 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
) as BitcoinFrostWallet;
|
) as BitcoinFrostWallet;
|
||||||
|
|
||||||
_threshold = wallet.frostInfo.threshold;
|
_threshold = wallet.frostInfo.threshold;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
ref.read(pFrostMyName.state).state = wallet.frostInfo.myName;
|
||||||
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
"Share this config with the group members. ",
|
"Share this config with the group members. ",
|
||||||
style: STextStyles.w600_12(context),
|
style: STextStyles.w500_12(context),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
|
@ -159,7 +162,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 20 : 16,
|
height: Util.isDesktop ? 20 : 12,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: qrImageSize,
|
height: qrImageSize,
|
||||||
|
@ -179,7 +182,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 20 : 16,
|
height: Util.isDesktop ? 20 : 12,
|
||||||
),
|
),
|
||||||
DetailItem(
|
DetailItem(
|
||||||
title: "Encoded transaction config",
|
title: "Encoded transaction config",
|
||||||
|
@ -193,7 +196,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 20 : 16,
|
height: Util.isDesktop ? 20 : 12,
|
||||||
),
|
),
|
||||||
DetailItem(
|
DetailItem(
|
||||||
title: "Threshold",
|
title: "Threshold",
|
||||||
|
@ -201,7 +204,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
horizontal: true,
|
horizontal: true,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 20 : 16,
|
height: Util.isDesktop ? 20 : 12,
|
||||||
),
|
),
|
||||||
if (!Util.isDesktop)
|
if (!Util.isDesktop)
|
||||||
const Spacer(
|
const Spacer(
|
||||||
|
@ -217,7 +220,7 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: Util.isDesktop ? 20 : 16,
|
height: Util.isDesktop ? 20 : 12,
|
||||||
),
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Attempt sign",
|
label: "Attempt sign",
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
import 'package:stackwallet/widgets/frost_step_user_steps.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
@ -41,7 +42,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
||||||
late final TextEditingController configFieldController;
|
late final TextEditingController configFieldController;
|
||||||
late final FocusNode configFocusNode;
|
late final FocusNode configFocusNode;
|
||||||
|
|
||||||
bool _configEmpty = true;
|
bool _configEmpty = true, _userVerifyContinue = false;
|
||||||
|
|
||||||
bool _attemptSignLock = false;
|
bool _attemptSignLock = false;
|
||||||
|
|
||||||
|
@ -125,6 +126,12 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
||||||
void initState() {
|
void initState() {
|
||||||
configFieldController = TextEditingController();
|
configFieldController = TextEditingController();
|
||||||
configFocusNode = FocusNode();
|
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();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +153,7 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
||||||
const FrostStepUserSteps(
|
const FrostStepUserSteps(
|
||||||
userSteps: info,
|
userSteps: info,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 20),
|
||||||
FrostStepField(
|
FrostStepField(
|
||||||
controller: configFieldController,
|
controller: configFieldController,
|
||||||
focusNode: configFocusNode,
|
focusNode: configFocusNode,
|
||||||
|
@ -159,16 +166,25 @@ class _FrostSendStep1bState extends ConsumerState<FrostSendStep1b> {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
if (!Util.isDesktop) const Spacer(),
|
if (!Util.isDesktop) const Spacer(),
|
||||||
const SizedBox(
|
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(
|
PrimaryButton(
|
||||||
label: "Start signing",
|
label: "Start signing",
|
||||||
enabled: !_configEmpty,
|
enabled: !_configEmpty && _userVerifyContinue,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_attemptSign();
|
_attemptSign();
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:barcode_scan2/barcode_scan2.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/frost_route_generator.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/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/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/services/frost.dart';
|
import 'package:stackwallet/services/frost.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.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/custom_buttons/simple_copy_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/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/rounded_white_container.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostSendStep2 extends ConsumerStatefulWidget {
|
class FrostSendStep2 extends ConsumerStatefulWidget {
|
||||||
const FrostSendStep2({super.key});
|
const FrostSendStep2({super.key});
|
||||||
|
@ -47,7 +40,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
bool hasEnoughPreprocesses() {
|
int countPreprocesses() {
|
||||||
// own preprocess is not included in controllers and must be set here
|
// own preprocess is not included in controllers and must be set here
|
||||||
int count = 1;
|
int count = 1;
|
||||||
|
|
||||||
|
@ -57,7 +50,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count >= threshold;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -124,11 +117,14 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"1.",
|
"2.",
|
||||||
style: STextStyles.w500_12(context),
|
style: STextStyles.w500_12(context),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -141,7 +137,7 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
"Enter their preprocesses into the corresponding fields. ",
|
"Enter their preprocesses into the corresponding fields. ",
|
||||||
style: STextStyles.w600_12(context),
|
style: STextStyles.w500_12(context),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: "You must have the threshold number of "
|
text: "You must have the threshold number of "
|
||||||
|
@ -164,9 +160,24 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Threshold",
|
||||||
|
detail: "$threshold signatures",
|
||||||
|
horizontal: true,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
DetailItem(
|
DetailItem(
|
||||||
title: "My name",
|
title: "My name",
|
||||||
detail: myName,
|
detail: myName,
|
||||||
|
button: Util.isDesktop
|
||||||
|
? IconCopyButton(
|
||||||
|
data: myName,
|
||||||
|
)
|
||||||
|
: SimpleCopyButton(
|
||||||
|
data: myName,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
|
@ -189,141 +200,46 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
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(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||||
Column(
|
FrostStepField(
|
||||||
mainAxisSize: MainAxisSize.min,
|
label: participantsWithoutMe[i],
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
hint: "Enter ${participantsWithoutMe[i]}'s preprocess",
|
||||||
children: [
|
controller: controllers[i],
|
||||||
Padding(
|
focusNode: focusNodes[i],
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
onChanged: (_) {
|
||||||
child: ClipRRect(
|
setState(() {
|
||||||
borderRadius: BorderRadius.circular(
|
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||||
Constants.size.circularBorderRadius,
|
});
|
||||||
),
|
},
|
||||||
child: TextField(
|
showQrScanOption: true,
|
||||||
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(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -332,8 +248,8 @@ class _FrostSendStep2State extends ConsumerState<FrostSendStep2> {
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Continue signing",
|
label: "Generate shares",
|
||||||
enabled: hasEnoughPreprocesses(),
|
enabled: countPreprocesses() >= threshold,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// collect Preprocess strings (not including my own)
|
// collect Preprocess strings (not including my own)
|
||||||
final preprocesses = controllers.map((e) => e.text).toList();
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:stackwallet/frost_route_generator.dart';
|
import 'package:stackwallet/frost_route_generator.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||||
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
import 'package:stackwallet/providers/frost_wallet/frost_wallet_providers.dart';
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/services/frost.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/logger.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/checkbox_text_button.dart';
|
||||||
import 'package:stackwallet/widgets/custom_buttons/frost_qr_dialog_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/custom_buttons/simple_copy_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
|
import 'package:stackwallet/widgets/frost_step_user_steps.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_dialog.dart';
|
||||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
import 'package:stackwallet/widgets/textfields/frost_step_field.dart';
|
||||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
|
||||||
|
|
||||||
class FrostSendStep3 extends ConsumerStatefulWidget {
|
class FrostSendStep3 extends ConsumerStatefulWidget {
|
||||||
const FrostSendStep3({super.key});
|
const FrostSendStep3({super.key});
|
||||||
|
@ -34,6 +30,11 @@ class FrostSendStep3 extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
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<TextEditingController> controllers = [];
|
||||||
final List<FocusNode> focusNodes = [];
|
final List<FocusNode> focusNodes = [];
|
||||||
|
|
||||||
|
@ -45,6 +46,8 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
|
|
||||||
final List<bool> fieldIsEmptyFlags = [];
|
final List<bool> fieldIsEmptyFlags = [];
|
||||||
|
|
||||||
|
bool _userVerifyContinue = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final wallet = ref.read(pWallets).getWallet(
|
final wallet = ref.read(pWallets).getWallet(
|
||||||
|
@ -93,15 +96,28 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
DetailItem(
|
const FrostStepUserSteps(
|
||||||
title: "My name",
|
userSteps: info,
|
||||||
detail: myName,
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
DetailItem(
|
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,
|
detail: myShare,
|
||||||
button: Util.isDesktop
|
button: Util.isDesktop
|
||||||
? IconCopyButton(
|
? IconCopyButton(
|
||||||
|
@ -125,133 +141,17 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < participantsWithoutMe.length; i++)
|
for (int i = 0; i < participantsWithoutMe.length; i++)
|
||||||
Column(
|
FrostStepField(
|
||||||
mainAxisSize: MainAxisSize.min,
|
label: participantsWithoutMe[i],
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
hint: "Enter ${participantsWithoutMe[i]}'s share",
|
||||||
children: [
|
controller: controllers[i],
|
||||||
Padding(
|
focusNode: focusNodes[i],
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
onChanged: (_) {
|
||||||
child: ClipRRect(
|
setState(() {
|
||||||
borderRadius: BorderRadius.circular(
|
fieldIsEmptyFlags[i] = controllers[i].text.isEmpty;
|
||||||
Constants.size.circularBorderRadius,
|
});
|
||||||
),
|
},
|
||||||
child: TextField(
|
showQrScanOption: true,
|
||||||
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(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -259,22 +159,22 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
CheckboxTextButton(
|
||||||
|
label: "I have verified that everyone has my share",
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_userVerifyContinue = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Complete signing",
|
label: "Generate transaction",
|
||||||
|
enabled: _userVerifyContinue &&
|
||||||
|
!fieldIsEmptyFlags.reduce((v, e) => v |= e),
|
||||||
onPressed: () async {
|
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
|
// collect Share strings
|
||||||
final sharesCollected = controllers.map((e) => e.text).toList();
|
final sharesCollected = controllers.map((e) => e.text).toList();
|
||||||
|
|
||||||
|
@ -295,10 +195,32 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
shares: shares,
|
shares: shares,
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.read(pFrostTxData.state).state =
|
final tx = cl.Transaction.fromHex(rawTx);
|
||||||
ref.read(pFrostTxData.state).state!.copyWith(
|
final txData = ref.read(pFrostTxData)!;
|
||||||
raw: rawTx,
|
|
||||||
);
|
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;
|
ref.read(pFrostCreateCurrentStep.state).state = 4;
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
|
@ -313,13 +235,15 @@ class _FrostSendStep3State extends ConsumerState<FrostSendStep3> {
|
||||||
level: LogLevel.Fatal,
|
level: LogLevel.Fatal,
|
||||||
);
|
);
|
||||||
|
|
||||||
return await showDialog<void>(
|
if (context.mounted) {
|
||||||
context: context,
|
return await showDialog<void>(
|
||||||
builder: (_) => StackOkDialog(
|
context: context,
|
||||||
title: "Failed to complete signing process",
|
builder: (_) => StackOkDialog(
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
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/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/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/wallet_view/wallet_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_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/frost_wallet/frost_wallet_providers.dart';
|
||||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/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/logger.dart';
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
import 'package:stackwallet/utilities/show_loading.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/util.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/custom_buttons/simple_copy_button.dart';
|
||||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||||
import 'package:stackwallet/widgets/detail_item.dart';
|
import 'package:stackwallet/widgets/detail_item.dart';
|
||||||
|
import 'package:stackwallet/widgets/expandable.dart';
|
||||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||||
|
|
||||||
class FrostSendStep4 extends ConsumerStatefulWidget {
|
class FrostSendStep4 extends ConsumerStatefulWidget {
|
||||||
|
@ -27,46 +35,154 @@ class FrostSendStep4 extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
|
final List<bool> _expandedStates = [];
|
||||||
|
|
||||||
bool _broadcastLock = false;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
if (kDebugMode)
|
||||||
height: 220,
|
DetailItem(
|
||||||
child: Row(
|
title: "Tx hex (debug mode only)",
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
detail: ref.watch(pFrostTxData)!.raw!,
|
||||||
children: [
|
button: Util.isDesktop
|
||||||
QrImageView(
|
? IconCopyButton(
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
data: ref.watch(pFrostTxData)!.raw!,
|
||||||
size: 220,
|
)
|
||||||
backgroundColor:
|
: SimpleCopyButton(
|
||||||
Theme.of(context).extension<StackColors>()!.background,
|
data: ref.watch(pFrostTxData)!.raw!,
|
||||||
foregroundColor: Theme.of(context)
|
),
|
||||||
.extension<StackColors>()!
|
|
||||||
.accentColorDark,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
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(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
DetailItem(
|
DetailItem(
|
||||||
title: "Raw transaction hex",
|
title: "Total",
|
||||||
detail: ref.watch(pFrostTxData.state).state!.raw!,
|
detail: ref.watch(pAmountFormatter(cryptoCurrency.coin)).format(
|
||||||
button: Util.isDesktop
|
ref.watch(pFrostTxData)!.fee! +
|
||||||
? IconCopyButton(
|
recipients.map((e) => e.amount).reduce((v, e) => v += e)),
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
horizontal: true,
|
||||||
)
|
),
|
||||||
: SimpleCopyButton(
|
const SizedBox(
|
||||||
data: ref.watch(pFrostTxData.state).state!.raw!,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Note",
|
||||||
|
detail: ref.watch(pFrostTxData)!.note ?? "",
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
DetailItem(
|
||||||
|
title: "Signers",
|
||||||
|
detail: signers,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
|
@ -76,7 +192,7 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
label: "Broadcast Transaction",
|
label: "Approve transaction",
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_broadcastLock) {
|
if (_broadcastLock) {
|
||||||
return;
|
return;
|
||||||
|
@ -92,11 +208,11 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
ref.read(pFrostScaffoldArgs)!.walletId!,
|
ref.read(pFrostScaffoldArgs)!.walletId!,
|
||||||
)
|
)
|
||||||
.confirmSend(
|
.confirmSend(
|
||||||
txData: ref.read(pFrostTxData.state).state!,
|
txData: ref.read(pFrostTxData)!,
|
||||||
),
|
),
|
||||||
context: context,
|
context: context,
|
||||||
message: "Broadcasting transaction to network",
|
message: "Broadcasting transaction to network",
|
||||||
isDesktop: Util.isDesktop,
|
isDesktop: true, // used to pop using root nav
|
||||||
onException: (e) {
|
onException: (e) {
|
||||||
ex = e;
|
ex = e;
|
||||||
},
|
},
|
||||||
|
@ -106,7 +222,7 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
throw ex!;
|
throw ex!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (context.mounted) {
|
||||||
if (txData != null) {
|
if (txData != null) {
|
||||||
ref.read(pFrostTxData.state).state = txData;
|
ref.read(pFrostTxData.state).state = txData;
|
||||||
Navigator.of(context).popUntil(
|
Navigator.of(context).popUntil(
|
||||||
|
@ -123,15 +239,18 @@ class _FrostSendStep4State extends ConsumerState<FrostSendStep4> {
|
||||||
"$e\n$s",
|
"$e\n$s",
|
||||||
level: LogLevel.Fatal,
|
level: LogLevel.Fatal,
|
||||||
);
|
);
|
||||||
|
if (context.mounted) {
|
||||||
return await showDialog<void>(
|
return await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => StackOkDialog(
|
builder: (_) => StackOkDialog(
|
||||||
title: "Broadcast error",
|
title: "Broadcast error",
|
||||||
message: e.toString(),
|
message: e.toString(),
|
||||||
desktopPopRootNavigator: Util.isDesktop,
|
desktopPopRootNavigator: Util.isDesktop,
|
||||||
),
|
onOkPressed:
|
||||||
);
|
Navigator.of(context, rootNavigator: true).pop,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
_broadcastLock = false;
|
_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;
|
final String? changeAddress;
|
||||||
|
|
||||||
|
// frost specific
|
||||||
final String? frostMSConfig;
|
final String? frostMSConfig;
|
||||||
|
final List<String>? frostSigners;
|
||||||
|
|
||||||
// paynym specific
|
// paynym specific
|
||||||
final PaynymAccountLite? paynymAccountLite;
|
final PaynymAccountLite? paynymAccountLite;
|
||||||
|
@ -91,6 +93,7 @@ class TxData {
|
||||||
this.usedUTXOs,
|
this.usedUTXOs,
|
||||||
this.changeAddress,
|
this.changeAddress,
|
||||||
this.frostMSConfig,
|
this.frostMSConfig,
|
||||||
|
this.frostSigners,
|
||||||
this.paynymAccountLite,
|
this.paynymAccountLite,
|
||||||
this.web3dartTransaction,
|
this.web3dartTransaction,
|
||||||
this.nonce,
|
this.nonce,
|
||||||
|
@ -166,6 +169,7 @@ class TxData {
|
||||||
})>?
|
})>?
|
||||||
recipients,
|
recipients,
|
||||||
String? frostMSConfig,
|
String? frostMSConfig,
|
||||||
|
List<String>? frostSigners,
|
||||||
String? changeAddress,
|
String? changeAddress,
|
||||||
PaynymAccountLite? paynymAccountLite,
|
PaynymAccountLite? paynymAccountLite,
|
||||||
web3dart.Transaction? web3dartTransaction,
|
web3dart.Transaction? web3dartTransaction,
|
||||||
|
@ -209,6 +213,7 @@ class TxData {
|
||||||
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
usedUTXOs: usedUTXOs ?? this.usedUTXOs,
|
||||||
recipients: recipients ?? this.recipients,
|
recipients: recipients ?? this.recipients,
|
||||||
frostMSConfig: frostMSConfig ?? this.frostMSConfig,
|
frostMSConfig: frostMSConfig ?? this.frostMSConfig,
|
||||||
|
frostSigners: frostSigners ?? this.frostSigners,
|
||||||
changeAddress: changeAddress ?? this.changeAddress,
|
changeAddress: changeAddress ?? this.changeAddress,
|
||||||
paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite,
|
paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite,
|
||||||
web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction,
|
web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction,
|
||||||
|
@ -249,7 +254,7 @@ class TxData {
|
||||||
'recipients: $recipients, '
|
'recipients: $recipients, '
|
||||||
'utxos: $utxos, '
|
'utxos: $utxos, '
|
||||||
'usedUTXOs: $usedUTXOs, '
|
'usedUTXOs: $usedUTXOs, '
|
||||||
'frostMSConfig: $frostMSConfig, '
|
'frostSigners: $frostSigners, '
|
||||||
'changeAddress: $changeAddress, '
|
'changeAddress: $changeAddress, '
|
||||||
'paynymAccountLite: $paynymAccountLite, '
|
'paynymAccountLite: $paynymAccountLite, '
|
||||||
'web3dartTransaction: $web3dartTransaction, '
|
'web3dartTransaction: $web3dartTransaction, '
|
||||||
|
|
Loading…
Reference in a new issue