Skip to content

Commit 9550b4d

Browse files
committed
feat(withdraw): merge PR #3296 - add user confirm for manual withdraw input when equivalent to max (v0.9.3)
2 parents 1c1245c + 62cfea1 commit 9550b4d

File tree

1 file changed

+71
-4
lines changed

1 file changed

+71
-4
lines changed

lib/views/wallet/coin_details/withdraw_form/withdraw_form.dart

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_for
2525
import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_form/fields/fill_form_memo.dart';
2626
import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/trezor_withdraw_progress_dialog.dart';
2727
import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/withdraw_form_header.dart';
28+
import 'package:decimal/decimal.dart';
2829
import 'package:web_dex/views/wallet/coin_details/transactions/transaction_details.dart';
2930

3031
bool _isMemoSupportedProtocol(Asset asset) {
@@ -51,6 +52,7 @@ class WithdrawForm extends StatefulWidget {
5152
class _WithdrawFormState extends State<WithdrawForm> {
5253
late final WithdrawFormBloc _formBloc;
5354
late final _sdk = context.read<KomodoDefiSdk>();
55+
bool _suppressPreviewError = false;
5456
late final _mm2Api = context.read<Mm2Api>();
5557

5658
@override
@@ -78,6 +80,62 @@ class _WithdrawFormState extends State<WithdrawForm> {
7880
value: _formBloc,
7981
child: MultiBlocListener(
8082
listeners: [
83+
BlocListener<WithdrawFormBloc, WithdrawFormState>(
84+
listenWhen: (prev, curr) => prev.previewError != curr.previewError && curr.previewError != null,
85+
listener: (context, state) async {
86+
// If a preview failed and the user entered essentially their entire
87+
// spendable balance (but didn't select Max), offer to deduct the fee
88+
// by switching to max withdrawal.
89+
if (state.isMaxAmount) return;
90+
91+
final spendable = state.selectedSourceAddress?.balance.spendable;
92+
Decimal? entered;
93+
try {
94+
entered = Decimal.parse(state.amount);
95+
} catch (_) {
96+
entered = null;
97+
}
98+
99+
bool amountsMatchWithTolerance(Decimal a, Decimal b) {
100+
// Use a tiny epsilon to account for formatting/rounding differences
101+
const epsStr = '0.000000000000000001';
102+
final epsilon = Decimal.parse(epsStr);
103+
final diff = (a - b).abs();
104+
return diff <= epsilon;
105+
}
106+
107+
if (spendable != null && entered != null && amountsMatchWithTolerance(entered, spendable)) {
108+
if (mounted) setState(() { _suppressPreviewError = true; });
109+
final bloc = context.read<WithdrawFormBloc>();
110+
final agreed = await showDialog<bool>(
111+
context: context,
112+
builder: (context) => AlertDialog(
113+
title: Text(LocaleKeys.userActionRequired.tr()),
114+
content: const Text(
115+
'Since you\'re sending your full amount, the network fee will be deducted from the amount. Do you agree?',
116+
),
117+
actions: [
118+
TextButton(
119+
onPressed: () => Navigator.of(context).pop(false),
120+
child: Text(LocaleKeys.cancel.tr()),
121+
),
122+
FilledButton(
123+
onPressed: () => Navigator.of(context).pop(true),
124+
child: Text(LocaleKeys.ok.tr()),
125+
),
126+
],
127+
),
128+
);
129+
130+
if (mounted) setState(() { _suppressPreviewError = false; });
131+
132+
if (agreed == true) {
133+
bloc.add(const WithdrawFormMaxAmountEnabled(true));
134+
bloc.add(const WithdrawFormPreviewSubmitted());
135+
}
136+
}
137+
},
138+
),
81139
BlocListener<WithdrawFormBloc, WithdrawFormState>(
82140
listenWhen: (prev, curr) =>
83141
prev.step != curr.step && curr.step == WithdrawFormStep.success,
@@ -141,6 +199,7 @@ class _WithdrawFormState extends State<WithdrawForm> {
141199
],
142200
child: WithdrawFormContent(
143201
onBackButtonPressed: widget.onBackButtonPressed,
202+
suppressPreviewError: _suppressPreviewError,
144203
onSuccess: widget.onSuccess,
145204
),
146205
),
@@ -150,9 +209,15 @@ class _WithdrawFormState extends State<WithdrawForm> {
150209

151210
class WithdrawFormContent extends StatelessWidget {
152211
final VoidCallback? onBackButtonPressed;
212+
final bool suppressPreviewError;
153213
final VoidCallback onSuccess;
154214

155-
const WithdrawFormContent({required this.onSuccess, this.onBackButtonPressed, super.key});
215+
const WithdrawFormContent({
216+
required this.onSuccess,
217+
required this.suppressPreviewError,
218+
this.onBackButtonPressed,
219+
super.key,
220+
});
156221

157222
@override
158223
Widget build(BuildContext context) {
@@ -187,7 +252,7 @@ class WithdrawFormContent extends StatelessWidget {
187252
Widget _buildStep(WithdrawFormStep step) {
188253
switch (step) {
189254
case WithdrawFormStep.fill:
190-
return const WithdrawFormFillSection();
255+
return WithdrawFormFillSection(suppressPreviewError: suppressPreviewError);
191256
case WithdrawFormStep.confirm:
192257
return const WithdrawFormConfirmSection();
193258
case WithdrawFormStep.success:
@@ -502,7 +567,9 @@ class WithdrawResultDetails extends StatelessWidget {
502567
}
503568

504569
class WithdrawFormFillSection extends StatelessWidget {
505-
const WithdrawFormFillSection({super.key});
570+
final bool suppressPreviewError;
571+
572+
const WithdrawFormFillSection({required this.suppressPreviewError, super.key});
506573

507574
@override
508575
Widget build(BuildContext context) {
@@ -619,7 +686,7 @@ class WithdrawFormFillSection extends StatelessWidget {
619686
const SizedBox(height: 24),
620687
// TODO! Refactor to use Formz and replace with the appropriate
621688
// error state value.
622-
if (state.hasPreviewError)
689+
if (state.hasPreviewError && !suppressPreviewError)
623690
ErrorDisplay(
624691
message: LocaleKeys.withdrawPreviewError.tr(),
625692
detailedMessage: state.previewError!.message,

0 commit comments

Comments
 (0)