@@ -25,6 +25,7 @@ import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_for
2525import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/fill_form/fields/fill_form_memo.dart' ;
2626import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/trezor_withdraw_progress_dialog.dart' ;
2727import 'package:web_dex/views/wallet/coin_details/withdraw_form/widgets/withdraw_form_header.dart' ;
28+ import 'package:decimal/decimal.dart' ;
2829import 'package:web_dex/views/wallet/coin_details/transactions/transaction_details.dart' ;
2930
3031bool _isMemoSupportedProtocol (Asset asset) {
@@ -51,6 +52,7 @@ class WithdrawForm extends StatefulWidget {
5152class _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
151210class 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
504569class 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