fix-<-01-fiat-amounts (#3421)

* fix: handling of "< 0.1" Amounts in send flow * refactor: clean up imports and restructure classes in `send_page.dart` apply lint

Konstantin Ullrich committed Jul 20, 2026 at 13:19 UTC 6209e36dfcf536e9dad8f7d8b86144fd9a20f434
3 files changed +900 -827
lib/new-ui/pages/send_page.dart
+747 -681
@@ -1,85 +1,97 @@
1 -import 'package:cake_wallet/core/address_validator.dart';
2 -import 'package:cake_wallet/core/auth_service.dart';
3 -import 'package:cake_wallet/core/execution_state.dart';
4 -import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
5 -import 'package:cake_wallet/di.dart';
6 -import 'package:cake_wallet/entities/contact_record.dart';
7 -import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
8 -import 'package:cake_wallet/evm/evm.dart';
9 -import 'package:cake_wallet/exchange/trade.dart';
10 -import 'package:cake_wallet/generated/i18n.dart';
11 -import 'package:cake_wallet/monero/monero.dart';
12 -import 'package:cake_wallet/new-ui/modal_navigator.dart';
13 -import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
14 -import 'package:cake_wallet/new-ui/widgets/animated_dropdown.dart';
15 -import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
16 -import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
17 -import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
18 -import 'package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart';
19 -import 'package:cake_wallet/new-ui/widgets/picker.dart';
20 -import 'package:cake_wallet/new-ui/widgets/send_page/fiat_amount_bar.dart';
21 -import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_sheet.dart';
22 -import 'package:cake_wallet/new-ui/widgets/send_page/send_memo_input.dart';
23 -import 'package:cake_wallet/reactions/wallet_connect.dart';
24 -import 'package:cake_wallet/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart';
25 -import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
26 -import 'package:cake_wallet/src/widgets/new_list_row/list_item_regular_row_widget.dart';
27 -import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
28 -import 'package:cake_wallet/store/app_store.dart';
29 -import 'package:cw_core/amount/amount_sanitizer.dart';
30 -import 'package:cw_core/amount/money.dart';
31 -import 'package:cw_core/lnurl.dart';
32 -import 'package:cw_core/wallet_info.dart';
1 +import "package:cake_wallet/core/address_resolver/parsed_address.dart";
2 +import "package:cake_wallet/core/address_validator.dart";
3 +import "package:cake_wallet/core/auth_service.dart";
4 +import "package:cake_wallet/core/execution_state.dart";
5 +import "package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart";
6 +import "package:cake_wallet/di.dart";
7 +import "package:cake_wallet/entities/contact_record.dart";
8 +import "package:cake_wallet/entities/priority_for_wallet_type.dart";
9 +import "package:cake_wallet/evm/evm.dart";
10 +import "package:cake_wallet/exchange/trade.dart";
11 +import "package:cake_wallet/generated/i18n.dart";
12 +import "package:cake_wallet/main.dart";
13 +import "package:cake_wallet/monero/monero.dart";
14 +import "package:cake_wallet/new-ui/modal_navigator.dart";
15 +import "package:cake_wallet/new-ui/pages/coin_control_page.dart";
16 +import "package:cake_wallet/new-ui/widgets/animated_dropdown.dart";
17 +import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
18 +import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart";
19 +import "package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart";
20 +import "package:cake_wallet/new-ui/widgets/keyboard_hide_overlay.dart";
21 +import "package:cake_wallet/new-ui/widgets/modern_button.dart";
22 +import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
23 +import "package:cake_wallet/new-ui/widgets/picker.dart";
24 +import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
25 +import "package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart";
26 +import "package:cake_wallet/new-ui/widgets/send_page/fiat_amount_bar.dart";
27 +import "package:cake_wallet/new-ui/widgets/send_page/l2_action_wallet_selector.dart";
28 +import "package:cake_wallet/new-ui/widgets/send_page/recipient_dot_row.dart";
29 +import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart";
30 +import "package:cake_wallet/new-ui/widgets/send_page/send_amount_input.dart";
31 +import "package:cake_wallet/new-ui/widgets/send_page/send_confirm_sheet.dart";
32 +import "package:cake_wallet/new-ui/widgets/send_page/send_memo_input.dart";
33 +import "package:cake_wallet/new-ui/widgets/send_page/send_syncing_indicator.dart";
34 +import "package:cake_wallet/reactions/wallet_connect.dart";
35 +import "package:cake_wallet/routes.dart" show Routes;
36 +import "package:cake_wallet/src/screens/connect_device/connect_device_page.dart";
37 +import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
38 +import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
39 +import "package:cake_wallet/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart";
40 +import "package:cake_wallet/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart";
41 +import "package:cake_wallet/src/widgets/bottom_sheet/token_selection_bottom_sheet.dart";
42 +import "package:cake_wallet/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart";
43 +import "package:cake_wallet/src/widgets/cake_image_widget.dart";
44 +import "package:cake_wallet/src/widgets/new_list_row/list_item_regular_row_widget.dart";
45 +import "package:cake_wallet/src/widgets/primary_button.dart";
46 +import "package:cake_wallet/src/widgets/standard_checkbox.dart";
47 +import "package:cake_wallet/store/app_store.dart";
48 +import "package:cake_wallet/utils/payment_request.dart";
49 +import "package:cake_wallet/utils/show_pop_up.dart";
50 +import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart";
51 +import "package:cake_wallet/view_model/payment/payment_view_model.dart";
52 +import "package:cake_wallet/view_model/send/output.dart";
53 +import "package:cake_wallet/view_model/send/send_view_model.dart";
54 +import "package:cake_wallet/view_model/send/send_view_model_state.dart";
55 +import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
56 +import "package:cw_core/amount/amount_sanitizer.dart";
57 +import "package:cw_core/amount/money.dart";
58 +import "package:cw_core/crypto_currency.dart";
59 +import "package:cw_core/lnurl.dart";
60 +import "package:cw_core/transaction_priority.dart";
61 +import "package:cw_core/unspent_coin_type.dart";
62 +import "package:cw_core/utils/print_verbose.dart";
63 +import "package:cw_core/wallet_info.dart";
64 import "package:cw_core/wallet_type.dart";
34 -import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
35 -import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
36 -import 'package:cake_wallet/new-ui/widgets/send_page/directional_switcher.dart';
37 -import 'package:cake_wallet/new-ui/widgets/send_page/recipient_dot_row.dart';
38 -import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
39 -import 'package:cake_wallet/new-ui/widgets/send_page/send_amount_input.dart';
40 -import 'package:cake_wallet/new-ui/widgets/send_page/send_syncing_indicator.dart';
41 -import 'package:cake_wallet/routes.dart' show Routes;
42 -import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
43 -import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
44 -import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
45 -import 'package:cake_wallet/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart';
46 -import 'package:cake_wallet/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart';
47 -import 'package:cake_wallet/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart';
48 -import 'package:cake_wallet/src/widgets/primary_button.dart';
49 -import 'package:cake_wallet/utils/payment_request.dart';
50 -import 'package:cake_wallet/utils/show_pop_up.dart';
51 -import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
52 -import 'package:cake_wallet/view_model/send/output.dart';
53 -import 'package:cake_wallet/core/address_resolver/parsed_address.dart';
54 -import 'package:cake_wallet/view_model/send/send_view_model.dart';
55 -import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
56 -import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
57 -import 'package:cw_core/crypto_currency.dart';
58 -import 'package:cw_core/transaction_priority.dart';
59 -import 'package:cw_core/utils/print_verbose.dart';
60 -
61 -import 'package:cake_wallet/main.dart';
62 -import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
63 -import 'package:cake_wallet/new-ui/widgets/send_page/l2_action_wallet_selector.dart';
64 -import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
65 -import 'package:cw_core/unspent_coin_type.dart';
66 -import 'package:flutter/cupertino.dart';
67 -import 'package:flutter/material.dart';
68 -import 'package:flutter_mobx/flutter_mobx.dart';
69 -import 'package:mobx/mobx.dart';
70 -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
65 +import "package:flutter/cupertino.dart";
66 +import "package:flutter/material.dart";
67 +import "package:flutter_mobx/flutter_mobx.dart";
68 +import "package:mobx/mobx.dart";
69 +import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
70
71 class SendPageHelpContent {
72 + const SendPageHelpContent({
73 + required this.title,
74 + required this.imagePath,
75 + required this.description,
76 + this.disclaimer,
77 + });
78 +
79 final String imagePath;
80 final String title;
81 final String description;
82 final String? disclaimer;
77 -
78 - const SendPageHelpContent(
79 - {required this.title, required this.imagePath, required this.description, this.disclaimer});
83 }
84
85 class SendPageModes {
86 + const SendPageModes({
87 + required this.title,
88 + required this.showAddressField,
89 + this.description,
90 + this.confirmSheetIconPath,
91 + this.helpContent,
92 + this.popOnConfirmation = true,
93 + });
94 +
95 final bool showAddressField;
96 final String title;
97 final String? description;
@@ -87,87 +99,87 @@ class SendPageModes {
99 final SendPageHelpContent? helpContent;
100 final bool popOnConfirmation;
101
90 - const SendPageModes(
91 - {required this.title,
92 - this.description,
93 - required this.showAddressField,
94 - this.confirmSheetIconPath,
95 - this.helpContent,
96 - this.popOnConfirmation = true});
97 -
102 static final SendPageModes normal = SendPageModes(title: S.current.send, showAddressField: true);
103
104 static final SendPageModes lightningDeposit = SendPageModes(
105 + title: S.current.bitcoin_lightning_deposit,
106 + description: S.current.to_lightning,
107 + showAddressField: false,
108 + helpContent: SendPageHelpContent(
109 title: S.current.bitcoin_lightning_deposit,
102 - description: S.current.to_lightning,
103 - showAddressField: false,
104 - helpContent: SendPageHelpContent(
105 - title: S.current.bitcoin_lightning_deposit,
106 - imagePath: "assets/new-ui/lightning_deposit_help.svg",
107 - description: S.current.lightning_deposit_desc,
108 - disclaimer: S.current.lightning_deposit_disclaimer),
109 - popOnConfirmation: false);
110 + imagePath: "assets/new-ui/lightning_deposit_help.svg",
111 + description: S.current.lightning_deposit_desc,
112 + disclaimer: S.current.lightning_deposit_disclaimer,
113 + ),
114 + popOnConfirmation: false,
115 + );
116
117 static final SendPageModes lightningWithdrawal = SendPageModes(
118 + title: S.current.bitcoin_lightning_withdraw,
119 + description: S.current.to_on_chain,
120 + showAddressField: false,
121 + helpContent: SendPageHelpContent(
122 title: S.current.bitcoin_lightning_withdraw,
113 - description: S.current.to_on_chain,
114 - showAddressField: false,
115 - helpContent: SendPageHelpContent(
116 - title: S.current.bitcoin_lightning_withdraw,
117 - imagePath: "assets/new-ui/lightning_withdraw_help.svg",
118 - description: S.current.lightning_withdraw_desc,
119 - disclaimer: S.current.lightning_withdraw_disclaimer),
120 - popOnConfirmation: false);
123 + imagePath: "assets/new-ui/lightning_withdraw_help.svg",
124 + description: S.current.lightning_withdraw_desc,
125 + disclaimer: S.current.lightning_withdraw_disclaimer,
126 + ),
127 + popOnConfirmation: false,
128 + );
129
130 static final SendPageModes mwebDeposit = SendPageModes(
123 - title: "${S.current.mask} Litecoin",
124 - showAddressField: false,
125 - confirmSheetIconPath: "assets/new-ui/mask.svg",
126 - helpContent: SendPageHelpContent(
127 - title: S.current.about_litecoin_privacy,
128 - imagePath: "assets/new-ui/mweb_help.svg",
129 - description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
130 - disclaimer: S.current.mweb_help_disclaimer),
131 - popOnConfirmation: false);
131 + title: "${S.current.mask} Litecoin",
132 + showAddressField: false,
133 + confirmSheetIconPath: "assets/new-ui/mask.svg",
134 + helpContent: SendPageHelpContent(
135 + title: S.current.about_litecoin_privacy,
136 + imagePath: "assets/new-ui/mweb_help.svg",
137 + description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
138 + disclaimer: S.current.mweb_help_disclaimer,
139 + ),
140 + popOnConfirmation: false,
141 + );
142
143 static final SendPageModes mwebWithdrawal = SendPageModes(
134 - title: "${S.current.unmask} Litecoin",
135 - showAddressField: false,
136 - confirmSheetIconPath: "assets/new-ui/unmask.svg",
137 - helpContent: SendPageHelpContent(
138 - title: S.current.about_litecoin_privacy,
139 - imagePath: "assets/new-ui/mweb_help.svg",
140 - description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
141 - disclaimer: S.current.mweb_help_disclaimer),
142 - popOnConfirmation: false);
144 + title: "${S.current.unmask} Litecoin",
145 + showAddressField: false,
146 + confirmSheetIconPath: "assets/new-ui/unmask.svg",
147 + helpContent: SendPageHelpContent(
148 + title: S.current.about_litecoin_privacy,
149 + imagePath: "assets/new-ui/mweb_help.svg",
150 + description: "${S.current.mweb_help_desc_1}\n\n${S.current.mweb_help_desc_2}",
151 + disclaimer: S.current.mweb_help_disclaimer,
152 + ),
153 + popOnConfirmation: false,
154 + );
155
156 static final all = [normal, lightningDeposit, lightningWithdrawal, mwebDeposit, mwebWithdrawal];
157 }
158
159 class SendPageParams {
148 - final PaymentRequest? initialPaymentRequest;
149 - final SendPageModes mode;
150 - final CryptoCurrency? initialCurrency;
151 - final UnspentCoinType unspentCoinType;
152 -
160 SendPageParams({
161 this.initialPaymentRequest,
162 SendPageModes? mode,
163 this.unspentCoinType = UnspentCoinType.any,
164 this.initialCurrency,
165 }) : mode = mode ?? SendPageModes.normal;
166 +
167 + final PaymentRequest? initialPaymentRequest;
168 + final SendPageModes mode;
169 + final CryptoCurrency? initialCurrency;
170 + final UnspentCoinType unspentCoinType;
171 }
172
173 class NewSendPage extends StatefulWidget {
162 - NewSendPage(
163 - {super.key,
164 - required this.sendViewModel,
165 - required this.paymentViewModel,
166 - required this.walletSwitcherViewModel,
167 - required this.contactListViewModel,
168 - required this.authService,
169 - required SendPageParams params})
170 - : initialPaymentRequest = params.initialPaymentRequest,
174 + NewSendPage({
175 + required this.sendViewModel,
176 + required this.paymentViewModel,
177 + required this.walletSwitcherViewModel,
178 + required this.contactListViewModel,
179 + required this.authService,
180 + required SendPageParams params,
181 + super.key,
182 + }) : initialPaymentRequest = params.initialPaymentRequest,
183 mode = params.mode {
184 if (params.initialCurrency != null) {
185 sendViewModel.selectedCryptoCurrency = params.initialCurrency!;
@@ -205,20 +217,20 @@ class _NewSendPageState extends State<NewSendPage> {
217 super.initState();
218 _addInputControllers();
219
208 - reaction((_) => widget.sendViewModel.outputs[_selectedOutput].sendAll, ((bool all) {
220 + reaction((_) => widget.sendViewModel.outputs[_selectedOutput].sendAll, (all) {
221 if (all) {
222 widget.sendViewModel.outputs[_selectedOutput].isFiatEntry = false;
223 _amountControllers[_selectedOutput].text = S.current.all;
224 }
213 - }));
225 + });
226
215 - reaction((_) => widget.sendViewModel.outputs[_selectedOutput].address, ((address) {
227 + reaction((_) => widget.sendViewModel.outputs[_selectedOutput].address, (address) {
228 if (_addressControllers[_selectedOutput].text != address) {
229 _addressControllers[_selectedOutput].text = address;
230 }
219 - }));
231 + });
232
221 - reaction((_) => widget.sendViewModel.outputs[_selectedOutput].memo, (String memo) {
233 + reaction((_) => widget.sendViewModel.outputs[_selectedOutput].memo, (memo) {
234 if (memo != _memoControllers[_selectedOutput].text) {
235 _memoControllers[_selectedOutput].text = memo;
236 }
@@ -270,7 +282,9 @@ class _NewSendPageState extends State<NewSendPage> {
282
283 Future<void> _resolveAddressForOutput(Output output) async {
284 final result = await widget.sendViewModel.resolveAddressForOutput(output);
273 - if (result == null) return;
285 + if (result == null || !mounted) {
286 + return;
287 + }
288
289 final confirmed = await showParsedAddressConfirmationAlert(context, result);
290
@@ -291,308 +305,320 @@ class _NewSendPageState extends State<NewSendPage> {
305 }
306
307 @override
294 - Widget build(BuildContext context) {
295 - return Observer(builder: (_) {
296 - final output = widget.sendViewModel.outputs[_selectedOutput];
297 - return SafeArea(
298 - bottom: false,
299 - child: KeyboardHideOverlay(
300 - unfocusOnTap: true,
301 - child: Container(
302 - decoration: BoxDecoration(
303 - color: Theme.of(context).colorScheme.surface,
304 - borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
305 - child: SafeArea(
306 - child: Column(
307 - spacing: 12,
308 - mainAxisSize: MainAxisSize.max,
309 - children: [
310 - ModalTopBar(
311 - title: widget.mode.title,
312 - subtitle: widget.mode.description,
313 - leadingIcon: Icon(Icons.close),
314 - onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
315 - trailingWidget: Observer(
316 - builder: (_) => Row(
317 - spacing: 8,
318 - children: [
319 - if (widget.sendViewModel.outputs.length > 1)
320 - ModernButton(
321 - size: 36,
322 - icon: CakeImageWidget(
323 - imageUrl: "assets/new-ui/remove_recipient.svg",
324 - colorFilter: ColorFilter.mode(
325 - Theme.of(context).colorScheme.primary,
326 - BlendMode.srcIn,
308 + Widget build(BuildContext context) => Observer(
309 + builder: (_) {
310 + final output = widget.sendViewModel.outputs[_selectedOutput];
311 + return SafeArea(
312 + bottom: false,
313 + child: KeyboardHideOverlay(
314 + unfocusOnTap: true,
315 + child: Container(
316 + decoration: BoxDecoration(
317 + color: Theme.of(context).colorScheme.surface,
318 + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
319 + ),
320 + child: SafeArea(
321 + child: Column(
322 + spacing: 12,
323 + mainAxisSize: MainAxisSize.max,
324 + children: [
325 + ModalTopBar(
326 + title: widget.mode.title,
327 + subtitle: widget.mode.description,
328 + leadingIcon: const Icon(Icons.close),
329 + onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
330 + trailingWidget: Observer(
331 + builder: (_) => Row(
332 + spacing: 8,
333 + children: [
334 + if (widget.sendViewModel.outputs.length > 1)
335 + ModernButton(
336 + size: 36,
337 + icon: CakeImageWidget(
338 + imageUrl: "assets/new-ui/remove_recipient.svg",
339 + colorFilter: ColorFilter.mode(
340 + Theme.of(context).colorScheme.primary,
341 + BlendMode.srcIn,
342 + ),
343 ),
344 + onPressed: () {
345 + final outputIndex = _selectedOutput;
346 + if (_selectedOutput != 0) {
347 + _setOutput(_selectedOutput - 1);
348 + } else {
349 + _setOutput(1);
350 + }
351 + _removeInputControllers(outputIndex);
352 + widget.sendViewModel.removeOutput(output);
353 + if (outputIndex == 0) {
354 + _setOutput(0);
355 + }
356 + },
357 ),
329 - onPressed: () {
330 - final outputIndex = _selectedOutput;
331 - if (_selectedOutput != 0) {
332 - _setOutput(_selectedOutput - 1);
333 - } else {
334 - _setOutput(1);
335 - }
336 - _removeInputControllers(outputIndex);
337 - widget.sendViewModel.removeOutput(output);
338 - if (outputIndex == 0) _setOutput(0);
339 - }),
340 - if (widget.mode == SendPageModes.normal &&
341 - widget.sendViewModel.hasMultiRecipient)
342 - ModernButton(
343 - size: 36,
344 - icon: Icon(Icons.add),
345 - onPressed: () {
346 - _addInputControllers();
347 - widget.sendViewModel.addOutput();
348 - _setOutput(widget.sendViewModel.outputs.length - 1);
349 - }),
350 - if (widget.mode.helpContent != null)
351 - ModernButton(
352 - size: 36,
353 - icon: CakeImageWidget(
354 - imageUrl: "assets/new-ui/help.svg",
355 - colorFilter: ColorFilter.mode(
356 - Theme.of(context).colorScheme.primary, BlendMode.srcIn),
358 + if (widget.mode == SendPageModes.normal &&
359 + widget.sendViewModel.hasMultiRecipient)
360 + ModernButton(
361 + size: 36,
362 + icon: const Icon(Icons.add),
363 + onPressed: () {
364 + _addInputControllers();
365 + widget.sendViewModel.addOutput();
366 + _setOutput(widget.sendViewModel.outputs.length - 1);
367 + },
368 ),
358 - onPressed: () {
359 - Navigator.of(context).push(CupertinoPageRoute(
360 - builder: (context) => Material(
361 - child: SendHelpPage(content: widget.mode.helpContent!))));
362 - })
363 - ],
364 - ),
365 - ),
366 - ),
367 - Expanded(
368 - child: Padding(
369 - padding: const EdgeInsets.symmetric(horizontal: 16.0),
370 - child: Column(
371 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
372 - mainAxisSize: MainAxisSize.max,
373 - children: [
374 - Form(
375 - key: _formKey,
376 - child: DirectionalAnimatedSwitcher(
377 - duration: Duration(milliseconds: 300),
378 - child: Column(
379 - key: ValueKey(_selectedOutput),
380 - spacing: 24,
381 - crossAxisAlignment: CrossAxisAlignment.start,
382 - children: [
383 - if (widget.mode.showAddressField)
384 - Column(
385 - crossAxisAlignment: CrossAxisAlignment.start,
386 - spacing: 12,
387 - children: [
388 - Text(S.of(context).address_or_alias),
389 - NewSendAddressInput(
390 - displayName: output.displayName,
391 - validator: output.isParsedAddress
392 - ? widget.sendViewModel.textValidator
393 - : widget.sendViewModel.addressValidator,
394 - addressController: _addressControllers[_selectedOutput],
395 - focusNode: _addressFocusNode,
396 - onURIScanned: (uri) async {
397 - output.resetParsedAddress();
398 - await _resolveAddressForOutput(output);
399 -
400 - // Process the payment through the new flow
401 - await _handlePaymentFlow(
402 - uri.toString(),
403 - PaymentRequest.fromString(uri.toString()),
404 - );
405 - },
406 - onEditingComplete: () {
407 - _addressFocusNode.unfocus();
408 - // output.fetchParsedAddress(context).then((val){
409 - // if(_addressControllers[_selectedOutput].text != output.extractedAddress) {
410 - // _addressControllers[_selectedOutput].text = output.extractedAddress;
411 - // }
412 - // });
413 - },
414 - onPushAddressBookButton: (context) async {
415 - output.resetParsedAddress();
416 - },
417 - onSelectedContact: (contact) {
418 - output.loadContact(contact);
419 - },
420 - onPushPasteButton: (context) async {
421 - if (_justHandledPasteButton) return;
422 - _justHandledPasteButton = true;
423 - try {
424 - output.resetParsedAddress();
425 - await _resolveAddressForOutput(output);
426 -
427 - final address = output.isParsedAddress
428 - ? output.extractedAddress
429 - : output.address;
430 -
431 - await _handlePaymentFlow(
432 - address,
433 - PaymentRequest(
434 - address,
435 - _amountControllers[_selectedOutput].text,
436 - _memoControllers[_selectedOutput].text,
437 - "",
438 - null,
439 - ),
440 - );
441 - } finally {
442 - _justHandledPasteButton = false;
443 - }
444 -
445 - _handleLightningInvoicePaste();
446 - },
447 - selectedCurrency:
448 - widget.sendViewModel.selectedCryptoCurrency,
449 - ),
450 - ],
369 + if (widget.mode.helpContent != null)
370 + ModernButton(
371 + size: 36,
372 + icon: CakeImageWidget(
373 + imageUrl: "assets/new-ui/help.svg",
374 + colorFilter: ColorFilter.mode(
375 + Theme.of(context).colorScheme.primary,
376 + BlendMode.srcIn,
377 ),
452 - Column(
378 + ),
379 + onPressed: () {
380 + Navigator.of(context).push(
381 + CupertinoPageRoute(
382 + builder: (context) => Material(
383 + child: SendHelpPage(content: widget.mode.helpContent!),
384 + ),
385 + ),
386 + );
387 + },
388 + ),
389 + ],
390 + ),
391 + ),
392 + ),
393 + Expanded(
394 + child: Padding(
395 + padding: const EdgeInsets.symmetric(horizontal: 16),
396 + child: Column(
397 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
398 + mainAxisSize: MainAxisSize.max,
399 + children: [
400 + Form(
401 + key: _formKey,
402 + child: DirectionalAnimatedSwitcher(
403 + duration: const Duration(milliseconds: 300),
404 + child: Column(
405 + key: ValueKey(_selectedOutput),
406 + spacing: 24,
407 crossAxisAlignment: CrossAxisAlignment.start,
454 - spacing: 12,
408 children: [
456 - Text(S.of(context).amount),
457 - NewSendAmountInput(
458 - validator: output.sendAll
459 - ? widget.sendViewModel.allAmountValidator
460 - : widget.sendViewModel.amountValidator(output),
461 - amountController: _amountControllers[_selectedOutput],
462 - currency: output.isFiatEntry
463 - ? widget.sendViewModel.fiatCurrency.title
464 - : widget.sendViewModel.selectedCryptoCurrencySymbol,
465 - currencyIconPath: output.isFiatEntry
466 - ? ""
467 - : widget.sendViewModel.selectedCryptoCurrency
468 - .iconPath ??
469 - "",
470 - hasPicker: (output.isFiatEntry ||
471 - widget.sendViewModel.hasMultipleTokens),
472 - onPickerClicked: () => _presentCurrencyPicker(context),
473 - maxDecimals: output.isFiatEntry
474 - ? widget.sendViewModel.fiatCurrency.decimals
475 - : widget.sendViewModel.useBaseUnits
476 - ? 0
477 - : widget
478 - .sendViewModel.selectedCryptoCurrency.decimals,
479 - ),
480 - FiatAmountBar(
481 - fiatInputMode: output.isFiatEntry,
482 - onSwitchButtonPressed: () {
483 - widget.sendViewModel.outputs[_selectedOutput]
484 - .isFiatEntry = !output.isFiatEntry;
485 - _amountControllers[_selectedOutput].text =
486 - output.isFiatEntry
487 - ? output.fiatAmount
488 - : output.displayCryptoAmount;
489 - },
490 - fiatAmount: _wrapAmount(output.roundedFiatAmount(6), 20),
491 - cryptoAmount:
492 - _wrapAmount(output.roundedCryptoAmount(6), 20),
493 - allAmount: widget.sendViewModel.balance,
494 - cryptoCurrencySymbol:
495 - widget.sendViewModel.selectedCryptoCurrencySymbol,
496 - fiatCurrencySymbol:
497 - widget.sendViewModel.fiatCurrency.symbol,
498 - onAllButtonPressed: () async {
499 - output.setSendAll(
500 - await widget.sendViewModel.sendingBalance);
501 - await output.calculateEstimatedFee();
502 - },
409 + if (widget.mode.showAddressField)
410 + Column(
411 + crossAxisAlignment: CrossAxisAlignment.start,
412 + spacing: 12,
413 + children: [
414 + Text(S.of(context).address_or_alias),
415 + NewSendAddressInput(
416 + displayName: output.displayName,
417 + validator: output.isParsedAddress
418 + ? widget.sendViewModel.textValidator
419 + : widget.sendViewModel.addressValidator,
420 + addressController:
421 + _addressControllers[_selectedOutput],
422 + focusNode: _addressFocusNode,
423 + onURIScanned: (uri) async {
424 + output.resetParsedAddress();
425 + await _resolveAddressForOutput(output);
426 +
427 + // Process the payment through the new flow
428 + await _handlePaymentFlow(
429 + uri.toString(),
430 + PaymentRequest.fromString(uri.toString()),
431 + );
432 + },
433 + onEditingComplete: _addressFocusNode.unfocus,
434 + onPushAddressBookButton: (_) {
435 + output.resetParsedAddress();
436 + },
437 + onSelectedContact: (contact) {
438 + output.loadContact(contact);
439 + },
440 + onPushPasteButton: (context) async {
441 + if (_justHandledPasteButton) {
442 + return;
443 + }
444 + _justHandledPasteButton = true;
445 + try {
446 + output.resetParsedAddress();
447 + await _resolveAddressForOutput(output);
448 +
449 + final address = output.isParsedAddress
450 + ? output.extractedAddress
451 + : output.address;
452 +
453 + await _handlePaymentFlow(
454 + address,
455 + PaymentRequest(
456 + address,
457 + _amountControllers[_selectedOutput].text,
458 + _memoControllers[_selectedOutput].text,
459 + "",
460 + null,
461 + ),
462 + );
463 + } finally {
464 + _justHandledPasteButton = false;
465 + }
466 +
467 + _handleLightningInvoicePaste();
468 + },
469 + selectedCurrency:
470 + widget.sendViewModel.selectedCryptoCurrency,
471 + ),
472 + ],
473 + ),
474 + Column(
475 + crossAxisAlignment: CrossAxisAlignment.start,
476 + spacing: 12,
477 + children: [
478 + Text(S.of(context).amount),
479 + NewSendAmountInput(
480 + validator: output.sendAll
481 + ? widget.sendViewModel.allAmountValidator
482 + : widget.sendViewModel.amountValidator(output),
483 + amountController: _amountControllers[_selectedOutput],
484 + currency: output.isFiatEntry
485 + ? widget.sendViewModel.fiatCurrency.title
486 + : widget.sendViewModel.selectedCryptoCurrencySymbol,
487 + currencyIconPath: output.isFiatEntry
488 + ? ""
489 + : widget.sendViewModel.selectedCryptoCurrency
490 + .iconPath ??
491 + "",
492 + hasPicker: output.isFiatEntry ||
493 + widget.sendViewModel.hasMultipleTokens,
494 + onPickerClicked: () => _presentCurrencyPicker(context),
495 + maxDecimals: output.isFiatEntry
496 + ? widget.sendViewModel.fiatCurrency.decimals
497 + : widget.sendViewModel.useBaseUnits
498 + ? 0
499 + : widget.sendViewModel.selectedCryptoCurrency
500 + .decimals,
501 + ),
502 + FiatAmountBar(
503 + fiatInputMode: output.isFiatEntry,
504 + onSwitchButtonPressed: _onFiatSwitchPressed,
505 + fiatAmount:
506 + _wrapAmount(output.roundedFiatAmount(6), 20),
507 + cryptoAmount:
508 + _wrapAmount(output.roundedCryptoAmount(6), 20),
509 + allAmount: widget.sendViewModel.balance,
510 + cryptoCurrencySymbol:
511 + widget.sendViewModel.selectedCryptoCurrencySymbol,
512 + fiatCurrencySymbol:
513 + widget.sendViewModel.fiatCurrency.symbol,
514 + onAllButtonPressed: () async {
515 + output.setSendAll(
516 + await widget.sendViewModel.sendingBalance,
517 + );
518 + await output.calculateEstimatedFee();
519 + },
520 + ),
521 + ],
522 ),
523 + if (widget.sendViewModel.isMwebAvailable &&
524 + widget.mode == SendPageModes.normal)
525 + StandardCheckbox(
526 + caption: S.of(context).litecoin_mweb_allow_coins,
527 + captionColor: Theme.of(context).colorScheme.onSurface,
528 + borderColor: Theme.of(context).colorScheme.primary,
529 + iconColor: Theme.of(context).colorScheme.primary,
530 + value: [UnspentCoinType.any, UnspentCoinType.mweb]
531 + .contains(widget.sendViewModel.coinTypeToSpendFrom),
532 + onChanged: (value) =>
533 + widget.sendViewModel.setAllowMwebCoins(value),
534 + ),
535 + if (widget.sendViewModel.hasMemos)
536 + Observer(
537 + builder: (_) => NewSendMemoInput(
538 + memoController: _memoControllers[_selectedOutput],
539 + maxMemoLength: widget.sendViewModel.maxMemoLength,
540 + memoLength: output.memo.length,
541 + ),
542 + ),
543 + if (widget.sendViewModel.hasCoinControl ||
544 + widget.sendViewModel.hasFees)
545 + AnimatedDropdown(
546 + dropdownText: S.of(context).advanced_settings,
547 + content: Column(
548 + children: [
549 + if (widget.sendViewModel.hasFees)
550 + ListItemRegularRowWidget(
551 + keyValue: "",
552 + label: S.of(context).fees,
553 + subtitle:
554 + "~${output.estimatedFee} ${widget.sendViewModel.currencySymbol} (${output.estimatedFeeFiatAmount} ${widget.sendViewModel.fiatCurrency})",
555 + onTap: () {
556 + if (widget.sendViewModel.feesViewModel
557 + .hasFeesPriority) {
558 + pickTransactionPriority(context, output);
559 + }
560 + },
561 + ),
562 + if (widget.sendViewModel.hasCoinControl)
563 + ListItemRegularRowWidget(
564 + keyValue: "",
565 + label: "Coin Control",
566 + onTap: () {
567 + showCupertinoModalBottomSheet(
568 + enableDrag: false,
569 + useRootNavigator: true,
570 + isDismissible: false,
571 + context: context,
572 + builder: (context) => NewCoinControlPage(
573 + unspentCoinsListViewModel: widget
574 + .sendViewModel
575 + .unspentCoinsListViewModel,
576 + canEdit: true,
577 + ),
578 + );
579 + },
580 + ),
581 + ],
582 + ),
583 + ),
584 ],
585 ),
506 - if (widget.sendViewModel.isMwebAvailable &&
507 - widget.mode == SendPageModes.normal)
508 - StandardCheckbox(
509 - caption: S.of(context).litecoin_mweb_allow_coins,
510 - captionColor: Theme.of(context).colorScheme.onSurface,
511 - borderColor: Theme.of(context).colorScheme.primary,
512 - iconColor: Theme.of(context).colorScheme.primary,
513 - value: [UnspentCoinType.any, UnspentCoinType.mweb]
514 - .contains(widget.sendViewModel.coinTypeToSpendFrom),
515 - onChanged: (value) =>
516 - widget.sendViewModel.setAllowMwebCoins(value),
517 - ),
518 - if (widget.sendViewModel.hasMemos)
519 - Observer(
520 - builder: (_) => NewSendMemoInput(
521 - memoController: _memoControllers[_selectedOutput],
522 - maxMemoLength: widget.sendViewModel.maxMemoLength,
523 - memoLength: output.memo.length,
524 - )),
525 - if (widget.sendViewModel.hasCoinControl ||
526 - widget.sendViewModel.hasFees)
527 - AnimatedDropdown(
528 - dropdownText: S.of(context).advanced_settings,
529 - content: Column(children: [
530 - if (widget.sendViewModel.hasFees)
531 - ListItemRegularRowWidget(
532 - keyValue: "",
533 - label: S.of(context).fees,
534 - subtitle:
535 - "~${output.estimatedFee} ${widget.sendViewModel.currencySymbol} (${output.estimatedFeeFiatAmount} ${widget.sendViewModel.fiatCurrency})",
536 - onTap: () {
537 - if (widget
538 - .sendViewModel.feesViewModel.hasFeesPriority)
539 - pickTransactionPriority(context, output);
540 - },
541 - ),
542 - if (widget.sendViewModel.hasCoinControl)
543 - ListItemRegularRowWidget(
544 - keyValue: "",
545 - label: "Coin Control",
546 - onTap: () {
547 - showCupertinoModalBottomSheet(
548 - enableDrag: false,
549 - useRootNavigator: true,
550 - isDismissible: false,
551 - context: context,
552 - builder: (context) {
553 - return NewCoinControlPage(
554 - unspentCoinsListViewModel: widget
555 - .sendViewModel
556 - .unspentCoinsListViewModel,
557 - canEdit: true);
558 - });
559 - }),
560 - ]),
561 - )
562 - ],
586 + ),
587 ),
564 - ),
565 - ),
566 - Observer(
567 - builder: (_) => Column(
568 - spacing: 12,
569 - children: [
570 - if (!widget.sendViewModel.isReadyForSend)
571 - SendSyncingIndicator(
572 - status: widget.sendViewModel.wallet.syncStatus),
573 - if (widget.sendViewModel.outputs.length > 1)
574 - RecipientDotRow(
575 - numDots: widget.sendViewModel.outputs.length,
576 - onSelected: _setOutput,
577 - selectedDot: _selectedOutput,
578 - ),
579 - Observer(
580 - builder: (_) {
581 - return LoadingPrimaryButton(
582 - key: ValueKey('send_page_send_button_key'),
583 - onPressed: () async {
584 - //Request dummy node to get the focus out of the text fields
585 - FocusScope.of(context).requestFocus(FocusNode());
586 -
587 - if (widget.sendViewModel.state is IsExecutingState) return;
588 -
589 - if (widget.mode == SendPageModes.normal) {
590 - _handleSend();
591 - } else if (widget.mode == SendPageModes.lightningDeposit ||
592 - widget.mode == SendPageModes.mwebDeposit) {
593 - Navigator.of(context).push(CupertinoPageRoute(
594 - builder: (context) => Material(
595 - child: L2ActionWalletSelector(
588 + Observer(
589 + builder: (_) => Column(
590 + spacing: 12,
591 + children: [
592 + if (!widget.sendViewModel.isReadyForSend)
593 + SendSyncingIndicator(
594 + status: widget.sendViewModel.wallet.syncStatus,
595 + ),
596 + if (widget.sendViewModel.outputs.length > 1)
597 + RecipientDotRow(
598 + numDots: widget.sendViewModel.outputs.length,
599 + onSelected: _setOutput,
600 + selectedDot: _selectedOutput,
601 + ),
602 + Observer(
603 + builder: (_) => LoadingPrimaryButton(
604 + key: const ValueKey("send_page_send_button_key"),
605 + onPressed: () {
606 + //Request dummy node to get the focus out of the text fields
607 + FocusScope.of(context).requestFocus(FocusNode());
608 +
609 + if (widget.sendViewModel.state is IsExecutingState) {
610 + return;
611 + }
612 +
613 + if (widget.mode == SendPageModes.normal) {
614 + _handleSend();
615 + } else if (widget.mode ==
616 + SendPageModes.lightningDeposit ||
617 + widget.mode == SendPageModes.mwebDeposit) {
618 + Navigator.of(context).push(
619 + CupertinoPageRoute(
620 + builder: (context) => Material(
621 + child: L2ActionWalletSelector(
622 showOtherWallets: false,
623 action: L2Actions.deposit,
624 sendViewModel: widget.sendViewModel,
@@ -601,13 +627,17 @@ class _NewSendPageState extends State<NewSendPage> {
627 walletSwitcherViewModel:
628 widget.walletSwitcherViewModel,
629 onSendInitiated: _handleSend,
604 - ))));
605 - } else if (widget.mode ==
606 - SendPageModes.lightningWithdrawal ||
607 - widget.mode == SendPageModes.mwebWithdrawal) {
608 - Navigator.of(context).push(CupertinoPageRoute(
609 - builder: (context) => Material(
610 - child: L2ActionWalletSelector(
630 + ),
631 + ),
632 + ),
633 + );
634 + } else if (widget.mode ==
635 + SendPageModes.lightningWithdrawal ||
636 + widget.mode == SendPageModes.mwebWithdrawal) {
637 + Navigator.of(context).push(
638 + CupertinoPageRoute(
639 + builder: (context) => Material(
640 + child: L2ActionWalletSelector(
641 showOtherWallets: false,
642 action: L2Actions.withdraw,
643 sendViewModel: widget.sendViewModel,
@@ -616,38 +646,48 @@ class _NewSendPageState extends State<NewSendPage> {
646 walletSwitcherViewModel:
647 widget.walletSwitcherViewModel,
648 onSendInitiated: _handleSend,
619 - ))));
620 - }
621 - },
622 - text: S.of(context).continue_text,
623 - color: Theme.of(context).colorScheme.primary,
624 - textColor: Theme.of(context).colorScheme.onPrimary,
625 - isLoading: widget.sendViewModel.state is IsExecutingState ||
626 - widget.sendViewModel.state is TransactionCommitting ||
627 - widget.sendViewModel.state
628 - is IsAwaitingDeviceResponseState ||
629 - widget.sendViewModel.state
630 - is LoadingTemplateExecutingState,
631 - isDisabled: !widget.sendViewModel.isReadyForSend ||
632 - widget.sendViewModel.state is ExecutedSuccessfullyState,
633 - );
634 - },
649 + ),
650 + ),
651 + ),
652 + );
653 + }
654 + },
655 + text: S.of(context).continue_text,
656 + color: Theme.of(context).colorScheme.primary,
657 + textColor: Theme.of(context).colorScheme.onPrimary,
658 + isLoading: widget.sendViewModel.state is IsExecutingState ||
659 + widget.sendViewModel.state is TransactionCommitting ||
660 + widget.sendViewModel.state
661 + is IsAwaitingDeviceResponseState ||
662 + widget.sendViewModel.state
663 + is LoadingTemplateExecutingState,
664 + isDisabled: !widget.sendViewModel.isReadyForSend ||
665 + widget.sendViewModel.state is ExecutedSuccessfullyState,
666 + ),
667 + ),
668 + const SizedBox(),
669 + ],
670 ),
636 - SizedBox(),
637 - ],
638 - ),
639 - )
640 - ],
671 + ),
672 + ],
673 + ),
674 + ),
675 ),
642 - ),
643 - )
644 - ],
676 + ],
677 + ),
678 + ),
679 ),
680 ),
647 - ),
648 - ),
681 + );
682 + },
683 );
650 - });
684 +
685 + void _onFiatSwitchPressed() {
686 + final output = widget.sendViewModel.outputs[_selectedOutput];
687 + widget.sendViewModel.outputs[_selectedOutput].isFiatEntry = !output.isFiatEntry;
688 +
689 + final amount = output.isFiatEntry ? output.fiatAmount : output.displayCryptoAmount;
690 + _amountControllers[_selectedOutput].text = amount.startsWith("<") ? "0" : amount;
691 }
692
693 void _setOutput(int index) {
@@ -667,7 +707,8 @@ class _NewSendPageState extends State<NewSendPage> {
707 _amountControllers[_amountControllers.length - 1].addListener(() {
708 if (_selectedOutput > widget.sendViewModel.outputs.length - 1) {
709 printV(
670 - "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!");
710 + "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!",
711 + );
712 return;
713 }
714
@@ -685,10 +726,14 @@ class _NewSendPageState extends State<NewSendPage> {
726 output.sendAll = false;
727 }
728
688 - if (S.current.all.contains(amount)) return;
729 + if (S.current.all.contains(amount)) {
730 + return;
731 + }
732
733 final cAmount = widget.sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
691 - output.cryptoAmount, widget.sendViewModel.selectedCryptoCurrency);
734 + output.cryptoAmount,
735 + widget.sendViewModel.selectedCryptoCurrency,
736 + );
737 if (amount != cAmount) {
738 final newAmount = widget.sendViewModel.amountParsingProxy
739 .getCanonicalCryptoAmount(amount, widget.sendViewModel.selectedCryptoCurrency);
@@ -700,7 +745,8 @@ class _NewSendPageState extends State<NewSendPage> {
745 _addressControllers[_amountControllers.length - 1].addListener(() {
746 if (_selectedOutput > widget.sendViewModel.outputs.length - 1) {
747 printV(
703 - "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!");
748 + "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!",
749 + );
750 return;
751 }
752
@@ -716,7 +762,8 @@ class _NewSendPageState extends State<NewSendPage> {
762 _memoControllers[_amountControllers.length - 1].addListener(() {
763 if (_selectedOutput > widget.sendViewModel.outputs.length - 1) {
764 printV(
719 - "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!");
765 + "_selectedOutput > widget.sendViewModel.outputs.length - 1! this should NOT happen!",
766 + );
767 return;
768 }
769 final memo = _memoControllers[_selectedOutput].text;
@@ -728,8 +775,8 @@ class _NewSendPageState extends State<NewSendPage> {
775 });
776 }
777
731 - void _handleSend() async {
732 - //TODO refactor this action. code was copied over from old ui. i don't like it.
778 + Future<void> _handleSend() async {
779 + //TODO(malik1004x): refactor this action. code was copied over from old ui. i don't like it.
780
781 for (var i = 0; i < widget.sendViewModel.outputs.length; i++) {
782 if (i < _amountControllers.length && !widget.sendViewModel.outputs[i].sendAll) {
@@ -737,7 +784,9 @@ class _NewSendPageState extends State<NewSendPage> {
784 widget.sendViewModel.outputs[i].setFiatAmount(_amountControllers[i].text);
785 } else {
786 final amount = widget.sendViewModel.amountParsingProxy.getCanonicalCryptoAmount(
740 - _amountControllers[i].text.sanitized(), widget.sendViewModel.selectedCryptoCurrency);
787 + _amountControllers[i].text.sanitized(),
788 + widget.sendViewModel.selectedCryptoCurrency,
789 + );
790 widget.sendViewModel.outputs[i].setCryptoAmount(amount);
791 }
792 }
@@ -763,34 +812,39 @@ class _NewSendPageState extends State<NewSendPage> {
812 if (widget.sendViewModel.wallet.isHardwareWallet) {
813 if (!widget.sendViewModel.hardwareWalletViewModel!
814 .isConnected(widget.sendViewModel.walletType)) {
766 - await Navigator.of(context).pushNamed(Routes.connectDevices,
767 - arguments: ConnectDevicePageParams(
768 - walletType: widget.sendViewModel.walletType,
769 - hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!,
770 - onConnectDevice: (_, __) {
771 - widget.sendViewModel.hardwareWalletViewModel!
772 - .initWallet(widget.sendViewModel.wallet);
773 - Navigator.of(context).pop();
774 - },
775 - ));
815 + await Navigator.of(context).pushNamed(
816 + Routes.connectDevices,
817 + arguments: ConnectDevicePageParams(
818 + walletType: widget.sendViewModel.walletType,
819 + hardwareWalletType: widget.sendViewModel.wallet.walletInfo.hardwareWalletType!,
820 + onConnectDevice: (_, __) {
821 + widget.sendViewModel.hardwareWalletViewModel!.initWallet(widget.sendViewModel.wallet);
822 + Navigator.of(context).pop();
823 + },
824 + ),
825 + );
826 } else {
777 - widget.sendViewModel.hardwareWalletViewModel!.initWallet(widget.sendViewModel.wallet);
827 + await widget.sendViewModel.hardwareWalletViewModel!.initWallet(widget.sendViewModel.wallet);
828 }
829 }
830
831 if (widget.sendViewModel.wallet.type == WalletType.monero) {
832 var amount = Money.zero(widget.sendViewModel.wallet.currency);
783 - for (var item in widget.sendViewModel.outputs) {
833 + for (final item in widget.sendViewModel.outputs) {
834 amount += item.cryptoAmountMoney;
835 }
836 if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
837 if (widget.sendViewModel.wallet.hardwareWalletType == HardwareWalletType.trezor) {
838 await monero!.syncTrezor(widget.sendViewModel.wallet);
839 } else {
790 - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
791 - arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet));
840 + if (mounted) {
841 + await Navigator.of(context).pushNamed(
842 + Routes.urqrAnimatedPage,
843 + arguments: monero!.exportOutputsUR(widget.sendViewModel.wallet),
844 + );
845 + }
846 }
793 - await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
847 + await Future.delayed(const Duration(seconds: 1)); // wait for monero to refresh the state
848 }
849 if (monero!.needExportOutputs(widget.sendViewModel.wallet, amount)) {
850 return;
@@ -807,21 +861,22 @@ class _NewSendPageState extends State<NewSendPage> {
861 Navigator.of(context, rootNavigator: true).pop();
862 }
863 showModalBottomSheet(
810 - isScrollControlled: true,
811 - isDismissible: false,
812 - enableDrag: false,
813 - context: navigatorKey.currentContext ?? context,
814 - backgroundColor: Colors.transparent,
815 - builder: (context) {
816 - return SendConfirmSheet(
817 - title: widget.mode.title,
818 - iconPath: widget.mode.helpContent?.imagePath,
819 - sendViewModel: widget.sendViewModel,
820 - );
821 - }).then((value) async {
864 + isScrollControlled: true,
865 + isDismissible: false,
866 + enableDrag: false,
867 + context: navigatorKey.currentContext ?? context,
868 + backgroundColor: Colors.transparent,
869 + builder: (context) => SendConfirmSheet(
870 + title: widget.mode.title,
871 + iconPath: widget.mode.helpContent?.imagePath,
872 + sendViewModel: widget.sendViewModel,
873 + ),
874 + ).then((value) {
875 if (widget.sendViewModel.state is TransactionCommitted &&
876 widget.mode.popOnConfirmation) {
824 - if (!mounted) return;
877 + if (!mounted) {
878 + return;
879 + }
880 Navigator.of(context, rootNavigator: true).pop();
881 }
882 widget.sendViewModel.dismissTransaction();
@@ -842,7 +897,9 @@ class _NewSendPageState extends State<NewSendPage> {
897 void _presentCurrencyPicker(BuildContext context) {
898 final output = widget.sendViewModel.outputs[_selectedOutput];
899
845 - if (!output.isFiatEntry && !widget.sendViewModel.hasMultipleTokens) return;
900 + if (!output.isFiatEntry && !widget.sendViewModel.hasMultipleTokens) {
901 + return;
902 + }
903
904 if (output.isFiatEntry) {
905 FiatCurrencyPickerSheet.show(
@@ -860,8 +917,8 @@ class _NewSendPageState extends State<NewSendPage> {
917 final balanceByAsset = <CryptoCurrency, CurrencyPickerBalance>{
918 for (final r in widget.sendViewModel.balanceViewModel.formattedBalances)
919 r.asset: CurrencyPickerBalance(
863 - amount: '${r.availableBalance} ${r.asset.title}',
864 - fiat: isFiatDisabled ? null : '${r.fiatAvailableBalanceRaw} ${r.fiatCurrency?.symbol}',
920 + amount: "${r.availableBalance} ${r.asset.title}",
921 + fiat: isFiatDisabled ? null : "${r.fiatAvailableBalanceRaw} ${r.fiatCurrency?.symbol}",
922 fiatValue: isFiatDisabled ? null : double.tryParse(r.fiatAvailableBalanceRaw),
923 ),
924 };
@@ -894,12 +951,16 @@ class _NewSendPageState extends State<NewSendPage> {
951 }
952
953 Future<void> _handlePaymentFlow(String uri, PaymentRequest paymentRequest) async {
897 - if (uri.contains('@') || paymentRequest.address.contains('@')) return;
954 + if (uri.contains("@") || paymentRequest.address.contains("@")) {
955 + return;
956 + }
957
958 if (OpenCryptoPayService.isOpenCryptoPayQR(uri) &&
959 widget.sendViewModel.selectedCryptoCurrency != CryptoCurrency.btcln) {
960 final request = await widget.sendViewModel.getOpenCryptoPayRequest(uri);
902 - if (request == null) return;
961 + if (request == null) {
962 + return;
963 + }
964 _applyPaymentRequest(request);
965 return;
966 }
@@ -960,7 +1021,7 @@ class _NewSendPageState extends State<NewSendPage> {
1021 break;
1022 }
1023 } catch (e) {
963 - printV('Payment flow error: $e');
1024 + printV("Payment flow error: $e");
1025 _applyPaymentRequest(paymentRequest);
1026 }
1027 }
@@ -979,34 +1040,32 @@ class _NewSendPageState extends State<NewSendPage> {
1040 context: context,
1041 isDismissible: true,
1042 isScrollControlled: true,
982 - builder: (BuildContext context) {
983 - return PaymentConfirmationBottomSheet(
984 - paymentFlowResult: result,
985 - paymentViewModel: paymentViewModel,
986 - walletSwitcherViewModel: walletSwitcherViewModel,
987 - paymentRequest: paymentRequest,
988 - onSelectWallet: () => _handleSelectWallet(
989 - paymentViewModel,
990 - walletSwitcherViewModel,
991 - paymentRequest,
992 - result,
993 - ),
994 - onChangeWallet: () => _handleChangeWallet(
995 - paymentViewModel,
996 - walletSwitcherViewModel,
997 - paymentRequest,
998 - result,
999 - ),
1000 - onSwap: (bottomSheetContext) =>
1001 - _handleSwapFlow(paymentViewModel, result, bottomSheetContext),
1002 - onSwitchNetwork: () => _handleSwitchNetwork(
1003 - paymentViewModel,
1004 - walletSwitcherViewModel,
1005 - paymentRequest,
1006 - result,
1007 - ),
1008 - );
1009 - },
1043 + builder: (context) => PaymentConfirmationBottomSheet(
1044 + paymentFlowResult: result,
1045 + paymentViewModel: paymentViewModel,
1046 + walletSwitcherViewModel: walletSwitcherViewModel,
1047 + paymentRequest: paymentRequest,
1048 + onSelectWallet: () => _handleSelectWallet(
1049 + paymentViewModel,
1050 + walletSwitcherViewModel,
1051 + paymentRequest,
1052 + result,
1053 + ),
1054 + onChangeWallet: () => _handleChangeWallet(
1055 + paymentViewModel,
1056 + walletSwitcherViewModel,
1057 + paymentRequest,
1058 + result,
1059 + ),
1060 + onSwap: (bottomSheetContext) =>
1061 + _handleSwapFlow(paymentViewModel, result, bottomSheetContext),
1062 + onSwitchNetwork: () => _handleSwitchNetwork(
1063 + paymentViewModel,
1064 + walletSwitcherViewModel,
1065 + paymentRequest,
1066 + result,
1067 + ),
1068 + ),
1069 );
1070 }
1071
@@ -1024,32 +1083,30 @@ class _NewSendPageState extends State<NewSendPage> {
1083 context: context,
1084 isDismissible: true,
1085 isScrollControlled: true,
1027 - builder: (BuildContext context) {
1028 - return TokenSelectionBottomSheet(
1029 - paymentViewModel: paymentViewModel,
1030 - paymentRequest: paymentRequest,
1031 - fixedNetwork: fixedNetwork,
1032 - onNext: (PaymentFlowResult newResult) {
1033 - final selectedChainId = newResult.chainId;
1034 - final isCompatible =
1035 - selectedChainId == evm?.getSelectedChainId(widget.sendViewModel.wallet);
1036 -
1037 - if (isCompatible) {
1038 - widget.sendViewModel.setSelectedCryptoCurrency(
1039 - newResult.addressDetectionResult!.detectedCurrency!.title,
1040 - );
1041 - _applyPaymentRequest(paymentRequest);
1042 - } else {
1043 - _showPaymentConfirmation(
1044 - paymentViewModel,
1045 - walletSwitcherViewModel,
1046 - paymentRequest,
1047 - newResult,
1048 - );
1049 - }
1050 - },
1051 - );
1052 - },
1086 + builder: (context) => TokenSelectionBottomSheet(
1087 + paymentViewModel: paymentViewModel,
1088 + paymentRequest: paymentRequest,
1089 + fixedNetwork: fixedNetwork,
1090 + onNext: (newResult) {
1091 + final selectedChainId = newResult.chainId;
1092 + final isCompatible =
1093 + selectedChainId == evm?.getSelectedChainId(widget.sendViewModel.wallet);
1094 +
1095 + if (isCompatible) {
1096 + widget.sendViewModel.setSelectedCryptoCurrency(
1097 + newResult.addressDetectionResult!.detectedCurrency!.title,
1098 + );
1099 + _applyPaymentRequest(paymentRequest);
1100 + } else {
1101 + _showPaymentConfirmation(
1102 + paymentViewModel,
1103 + walletSwitcherViewModel,
1104 + paymentRequest,
1105 + newResult,
1106 + );
1107 + }
1108 + },
1109 + ),
1110 );
1111 }
1112
@@ -1065,12 +1122,10 @@ class _NewSendPageState extends State<NewSendPage> {
1122 context: context,
1123 isDismissible: true,
1124 isScrollControlled: true,
1068 - builder: (BuildContext dialogContext) {
1069 - return WalletSwitcherBottomSheet(
1070 - viewModel: walletSwitcherViewModel,
1071 - filterWalletType: paymentViewModel.detectedWalletType,
1072 - );
1073 - },
1125 + builder: (dialogContext) => WalletSwitcherBottomSheet(
1126 + viewModel: walletSwitcherViewModel,
1127 + filterWalletType: paymentViewModel.detectedWalletType,
1128 + ),
1129 );
1130
1131 final success = await walletSwitcherViewModel.switchToSelectedWallet();
@@ -1115,7 +1170,7 @@ class _NewSendPageState extends State<NewSendPage> {
1170 showModalBottomSheet<void>(
1171 context: context,
1172 isDismissible: false,
1118 - builder: (BuildContext context) {
1173 + builder: (context) {
1174 loadingBottomSheetContext = context;
1175 return LoadingBottomSheet(
1176 titleText: S.of(context).loading_your_wallet,
@@ -1156,7 +1211,7 @@ class _NewSendPageState extends State<NewSendPage> {
1211 showModalBottomSheet<void>(
1212 context: context,
1213 isDismissible: false,
1159 - builder: (BuildContext context) {
1214 + builder: (context) {
1215 loadingBottomSheetContext = context;
1216 return LoadingBottomSheet(
1217 titleText: S.of(context).loading_your_wallet,
@@ -1195,7 +1250,9 @@ class _NewSendPageState extends State<NewSendPage> {
1250 PaymentRequest paymentRequest,
1251 PaymentFlowResult result,
1252 ) async {
1198 - if (result.type != PaymentFlowType.evmNetworkSelection || result.wallet == null) return;
1253 + if (result.type != PaymentFlowType.evmNetworkSelection || result.wallet == null) {
1254 + return;
1255 + }
1256
1257 if (context.mounted && Navigator.of(context).canPop()) {
1258 Navigator.of(context).pop();
@@ -1207,11 +1264,9 @@ class _NewSendPageState extends State<NewSendPage> {
1264 showModalBottomSheet<void>(
1265 context: context,
1266 isDismissible: false,
1210 - builder: (BuildContext context) {
1267 + builder: (context) {
1268 loadingBottomSheetContext = context;
1212 - return LoadingBottomSheet(
1213 - titleText: S.of(context).loading_your_wallet,
1214 - );
1269 + return LoadingBottomSheet(titleText: S.of(context).loading_your_wallet);
1270 },
1271 );
1272 }
@@ -1234,7 +1289,7 @@ class _NewSendPageState extends State<NewSendPage> {
1289 if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
1290 Navigator.of(loadingBottomSheetContext!).pop();
1291 }
1237 - printV('Switch network error: $e');
1292 + printV("Switch network error: $e");
1293 }
1294 }
1295
@@ -1246,9 +1301,11 @@ class _NewSendPageState extends State<NewSendPage> {
1301 _addressControllers[_selectedOutput].text = paymentRequest.address;
1302 if (paymentRequest.amount.isNotEmpty) {
1303 try {
1249 - _amountControllers[_selectedOutput].text = widget.sendViewModel.amountParsingProxy
1250 - .getDisplayCryptoAmount(
1251 - paymentRequest.amount, widget.sendViewModel.selectedCryptoCurrency);
1304 + _amountControllers[_selectedOutput].text =
1305 + widget.sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
1306 + paymentRequest.amount,
1307 + widget.sendViewModel.selectedCryptoCurrency,
1308 + );
1309 } catch (e) {}
1310 }
1311 _memoControllers[_selectedOutput].text = paymentRequest.note;
@@ -1263,14 +1320,16 @@ class _NewSendPageState extends State<NewSendPage> {
1320
1321 await Future.delayed(const Duration(milliseconds: 100));
1322
1266 - if (!mounted) return;
1323 + if (!mounted) {
1324 + return;
1325 + }
1326
1327 final bottomSheet = getIt.get<SwapConfirmationBottomSheet>(param1: result);
1328 await showModalBottomSheet<Trade?>(
1329 context: context,
1330 isDismissible: true,
1331 isScrollControlled: true,
1273 - builder: (BuildContext context) => bottomSheet,
1332 + builder: (context) => bottomSheet,
1333 );
1334 }
1335
@@ -1314,7 +1373,7 @@ class _NewSendPageState extends State<NewSendPage> {
1373 for (final type in supportedTypes) {
1374 final addressPattern = AddressValidator.getAddressFromStringPattern(type);
1375 if (addressPattern != null) {
1317 - final regex = RegExp('^$addressPattern\$');
1376 + final regex = RegExp("^$addressPattern\$");
1377 if (regex.hasMatch(trimmed)) {
1378 isValid = true;
1379 break;
@@ -1323,7 +1382,9 @@ class _NewSendPageState extends State<NewSendPage> {
1382 }
1383
1384 for (final pattern in excludedPatterns) {
1326 - if (pattern.hasMatch(trimmed)) return false;
1385 + if (pattern.hasMatch(trimmed)) {
1386 + return false;
1387 + }
1388 }
1389
1390 return isValid;
@@ -1341,116 +1402,123 @@ class _NewSendPageState extends State<NewSendPage> {
1402 await showCupertinoModalBottomSheet(
1403 context: pageContext,
1404 expand: false,
1344 - builder: (BuildContext modalContext) {
1405 + builder: (modalContext) {
1406 int selectedIdx = selectedItem;
1346 - return Observer(builder: (context) {
1347 - double? customFeeRate = isBitcoinWallet
1348 - ? widget.sendViewModel.feesViewModel.customBitcoinFeeRate.toDouble()
1349 - : null;
1350 - return StatefulBuilder(
1351 - builder: (BuildContext context, StateSetter setState) {
1352 - return IntrinsicHeight(
1407 + return Observer(
1408 + builder: (context) {
1409 + final customFeeRate = isBitcoinWallet
1410 + ? widget.sendViewModel.feesViewModel.customBitcoinFeeRate.toDouble()
1411 + : null;
1412 + return StatefulBuilder(
1413 + builder: (context, setState) => IntrinsicHeight(
1414 // height: MediaQuery.of(context).size.height*0.4,
1415 child: ModalNavigator(
1355 - parentContext: modalContext,
1356 - heightMode: ModalHeightModes.natural,
1357 - rootPage: Material(
1358 - child: NewPicker(
1359 - title: S.of(context).set_fees,
1360 - description: S.of(context).set_fees_desc,
1361 - sliderPageTitle: S.of(context).custom_fee,
1362 - sliderInitialValue: customFeeRate,
1363 - sliderMaxValue: maxCustomFeeRate,
1364 - sliderValueDescription: "sat/byte",
1365 - items: items
1366 - .map((item) => PickerItem<TransactionPriority>(
1367 - title: item.title,
1368 - subtitle: item.description,
1369 - hint: item.hint,
1370 - value: item,
1371 - isSliderItem: items.indexOf(item) == customItemIndex,
1372 - ))
1373 - .toList(),
1374 - onItemSelected: (TransactionPriority priority) async {
1375 - widget.sendViewModel.feesViewModel.setTransactionPriority(priority);
1376 - setState(() => selectedIdx = items.indexOf(priority));
1377 - await output.calculateEstimatedFee();
1378 - },
1379 - onSliderChanged: (double value) {
1380 - widget.sendViewModel.feesViewModel.customBitcoinFeeRate = value.round();
1381 - },
1382 - selectedIndex: selectedIdx),
1383 - )),
1384 - );
1385 - },
1386 - );
1387 - });
1416 + parentContext: modalContext,
1417 + heightMode: ModalHeightModes.natural,
1418 + rootPage: Material(
1419 + child: NewPicker(
1420 + title: S.of(context).set_fees,
1421 + description: S.of(context).set_fees_desc,
1422 + sliderPageTitle: S.of(context).custom_fee,
1423 + sliderInitialValue: customFeeRate,
1424 + sliderMaxValue: maxCustomFeeRate,
1425 + sliderValueDescription: "sat/byte",
1426 + items: items
1427 + .map(
1428 + (item) => PickerItem<TransactionPriority>(
1429 + title: item.title,
1430 + subtitle: item.description,
1431 + hint: item.hint,
1432 + value: item,
1433 + isSliderItem: items.indexOf(item) == customItemIndex,
1434 + ),
1435 + )
1436 + .toList(),
1437 + onItemSelected: (priority) async {
1438 + widget.sendViewModel.feesViewModel.setTransactionPriority(priority);
1439 + setState(() => selectedIdx = items.indexOf(priority));
1440 + await output.calculateEstimatedFee();
1441 + },
1442 + onSliderChanged: (value) {
1443 + widget.sendViewModel.feesViewModel.customBitcoinFeeRate = value.round();
1444 + },
1445 + selectedIndex: selectedIdx,
1446 + ),
1447 + ),
1448 + ),
1449 + ),
1450 + );
1451 + },
1452 + );
1453 },
1454 );
1455 }
1456
1392 - String _wrapAmount(String amount, int maxChars) {
1393 - return amount.length <= maxChars ? amount : "${amount.substring(0, maxChars - 3)}...";
1394 - }
1457 + String _wrapAmount(String amount, int maxChars) =>
1458 + amount.length <= maxChars ? amount : "${amount.substring(0, maxChars - 3)}...";
1459 }
1460
1461 class SendHelpPage extends StatelessWidget {
1398 - const SendHelpPage({super.key, required this.content});
1462 + const SendHelpPage({required this.content, super.key});
1463
1464 final SendPageHelpContent content;
1465
1466 @override
1403 - Widget build(BuildContext context) {
1404 - return SafeArea(
1405 - child: Column(
1406 - mainAxisSize: MainAxisSize.max,
1407 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
1408 - children: [
1409 - ModalTopBar(
1410 - title: content.title,
1411 - leadingIcon: Icon(Icons.arrow_back_ios_new),
1412 - onLeadingPressed: Navigator.of(context).pop,
1413 - ),
1414 - Padding(
1415 - padding: const EdgeInsets.symmetric(horizontal: 18.0),
1416 - child: Column(
1417 - spacing: 12,
1418 - mainAxisSize: MainAxisSize.max,
1419 - mainAxisAlignment: MainAxisAlignment.center,
1420 - children: [
1421 - CakeImageWidget(imageUrl: content.imagePath),
1422 - Text(
1423 - content.description,
1424 - textAlign: TextAlign.center,
1425 - style: TextStyle(
1467 + Widget build(BuildContext context) => SafeArea(
1468 + child: Column(
1469 + mainAxisSize: MainAxisSize.max,
1470 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
1471 + children: [
1472 + ModalTopBar(
1473 + title: content.title,
1474 + leadingIcon: const Icon(Icons.arrow_back_ios_new),
1475 + onLeadingPressed: Navigator.of(context).pop,
1476 + ),
1477 + Padding(
1478 + padding: const EdgeInsets.symmetric(horizontal: 18),
1479 + child: Column(
1480 + spacing: 12,
1481 + mainAxisSize: MainAxisSize.max,
1482 + mainAxisAlignment: MainAxisAlignment.center,
1483 + children: [
1484 + CakeImageWidget(imageUrl: content.imagePath),
1485 + Text(
1486 + content.description,
1487 + textAlign: TextAlign.center,
1488 + style: TextStyle(
1489 fontSize: 14,
1490 fontWeight: FontWeight.w400,
1428 - color: Theme.of(context).colorScheme.onSurface),
1429 - ),
1430 - if (content.disclaimer != null) ...[
1431 - SizedBox(),
1432 - SizedBox(),
1433 - Text(content.disclaimer!,
1491 + color: Theme.of(context).colorScheme.onSurface,
1492 + ),
1493 + ),
1494 + if (content.disclaimer != null) ...[
1495 + const SizedBox(),
1496 + const SizedBox(),
1497 + Text(
1498 + content.disclaimer!,
1499 textAlign: TextAlign.center,
1500 style: TextStyle(
1436 - fontSize: 14,
1437 - fontWeight: FontWeight.w400,
1438 - color: Theme.of(context).colorScheme.onSurfaceVariant)),
1439 - ]
1440 - ],
1501 + fontSize: 14,
1502 + fontWeight: FontWeight.w400,
1503 + color: Theme.of(context).colorScheme.onSurfaceVariant,
1504 + ),
1505 + ),
1506 + ],
1507 + ],
1508 + ),
1509 ),
1442 - ),
1443 - Padding(
1444 - padding: EdgeInsets.symmetric(horizontal: 18),
1510 + Padding(
1511 + padding: const EdgeInsets.symmetric(horizontal: 18),
1512 child: NewPrimaryButton(
1446 - onPressed: Navigator.of(context).pop,
1447 - text: S.of(context).i_understand,
1448 - color: Theme.of(context).colorScheme.primary,
1449 - textColor: Theme.of(context).colorScheme.onPrimary))
1450 - ],
1451 - ),
1452 - );
1453 - }
1513 + onPressed: Navigator.of(context).pop,
1514 + text: S.of(context).i_understand,
1515 + color: Theme.of(context).colorScheme.primary,
1516 + textColor: Theme.of(context).colorScheme.onPrimary,
1517 + ),
1518 + ),
1519 + ],
1520 + ),
1521 + );
1522 }
1523
1524 Future<bool> showParsedAddressConfirmationAlert(
@@ -1459,20 +1527,18 @@ Future<bool> showParsedAddressConfirmationAlert(
1527 ) async {
1528 final confirmed = await showPopUp<bool>(
1529 context: context,
1462 - builder: (BuildContext context) {
1463 - return AlertWithOneAction(
1464 - alertTitle: S.of(context).address_detected,
1465 - headerTitleText: parsedAddress.profileName.isEmpty ? null : parsedAddress.profileName,
1466 - headerImageProfileUrl: parsedAddress.profileImageUrl.isEmpty
1467 - ? parsedAddress.addressSource.iconPath
1468 - : parsedAddress.profileImageUrl,
1469 - alertContent: S.of(context).extracted_address_content(
1470 - '${parsedAddress.handle} (${parsedAddress.addressSource.label})',
1471 - ),
1472 - buttonText: S.of(context).ok,
1473 - buttonAction: () => Navigator.of(context).pop(true),
1474 - );
1475 - },
1530 + builder: (context) => AlertWithOneAction(
1531 + alertTitle: S.of(context).address_detected,
1532 + headerTitleText: parsedAddress.profileName.isEmpty ? null : parsedAddress.profileName,
1533 + headerImageProfileUrl: parsedAddress.profileImageUrl.isEmpty
1534 + ? parsedAddress.addressSource.iconPath
1535 + : parsedAddress.profileImageUrl,
1536 + alertContent: S.of(context).extracted_address_content(
1537 + "${parsedAddress.handle} (${parsedAddress.addressSource.label})",
1538 + ),
1539 + buttonText: S.of(context).ok,
1540 + buttonAction: () => Navigator.of(context).pop(true),
1541 + ),
1542 );
1543
1544 return confirmed ?? false;
lib/new-ui/widgets/send_page/fiat_amount_bar.dart
+62 -61
@@ -1,23 +1,24 @@
1 -import 'package:cake_wallet/generated/i18n.dart';
2 -import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
3 -import 'package:cw_core/crypto_amount_format.dart';
4 -import 'package:flutter/material.dart';
1 +import "package:cake_wallet/generated/i18n.dart";
2 +import "package:cake_wallet/new-ui/widgets/modern_button.dart";
3 +import "package:cw_core/crypto_amount_format.dart";
4 +import "package:flutter/material.dart";
5
6 class FiatAmountBar extends StatelessWidget {
7 - const FiatAmountBar(
8 - {super.key,
9 - required this.fiatInputMode,
10 - required this.onSwitchButtonPressed,
11 - this.onAllButtonPressed,
12 - required this.cryptoAmount,
13 - required this.fiatAmount,
14 - required this.cryptoCurrencySymbol,
15 - required this.fiatCurrencySymbol,
16 - this.allAmount,
17 - this.foregroundElementColor,
18 - this.textColor,
19 - this.allAmountColor,
20 - this.allAmountTextColor});
7 + const FiatAmountBar({
8 + required this.fiatInputMode,
9 + required this.onSwitchButtonPressed,
10 + required this.cryptoAmount,
11 + required this.fiatAmount,
12 + required this.cryptoCurrencySymbol,
13 + required this.fiatCurrencySymbol,
14 + super.key,
15 + this.onAllButtonPressed,
16 + this.allAmount,
17 + this.foregroundElementColor,
18 + this.textColor,
19 + this.allAmountColor,
20 + this.allAmountTextColor,
21 + });
22
23 final bool fiatInputMode;
24 final VoidCallback onSwitchButtonPressed;
@@ -34,42 +35,41 @@ class FiatAmountBar extends StatelessWidget {
35 final Color? allAmountTextColor;
36
37 @override
37 - Widget build(BuildContext context) {
38 - return Row(
39 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
40 - children: [
41 - Row(
42 - spacing: 8,
43 - children: [
44 - ModernButton.svg(
45 - backgroundColor: foregroundElementColor,
46 - size: 28,
47 - svgPath: "assets/new-ui/switch.svg",
48 - iconSize: 18,
49 - onPressed: onSwitchButtonPressed,
50 - ),
51 - GestureDetector(
52 - onTap: onSwitchButtonPressed,
53 - child: Text(
54 - fiatInputMode
55 - ? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} $cryptoCurrencySymbol"
56 - : "${fiatAmount.isEmpty ? "0" : fiatAmount} $fiatCurrencySymbol",
57 - style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
58 - ),
59 - ),
60 - ],
61 - ),
62 - if (allAmount != null && allAmount!.isNotEmpty)
38 + Widget build(BuildContext context) => Row(
39 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
40 + children: [
41 Row(
42 spacing: 8,
43 children: [
66 - Text(
67 - "${S.of(context).max}.",
68 - style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
44 + ModernButton.svg(
45 + backgroundColor: foregroundElementColor,
46 + size: 28,
47 + svgPath: "assets/new-ui/switch.svg",
48 + iconSize: 18,
49 + onPressed: onSwitchButtonPressed,
50 + ),
51 + GestureDetector(
52 + onTap: onSwitchButtonPressed,
53 + child: Text(
54 + fiatInputMode
55 + ? "${cryptoAmount.isEmpty ? "0" : cryptoAmount.withMaxDecimals(8)} $cryptoCurrencySymbol"
56 + : "${fiatAmount.isEmpty ? "0" : fiatAmount} $fiatCurrencySymbol",
57 + style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
58 + ),
59 ),
70 - Container(
71 - decoration: BoxDecoration(borderRadius: BorderRadius.circular(999999)),
72 - child: Material(
60 + ],
61 + ),
62 + if (allAmount != null && allAmount!.isNotEmpty)
63 + Row(
64 + spacing: 8,
65 + children: [
66 + Text(
67 + "${S.of(context).max}.",
68 + style: TextStyle(color: textColor ?? Theme.of(context).colorScheme.onSurface),
69 + ),
70 + Container(
71 + decoration: BoxDecoration(borderRadius: BorderRadius.circular(999999)),
72 + child: Material(
73 color: allAmountColor ??
74 foregroundElementColor ??
75 Theme.of(context).colorScheme.surfaceContainer,
@@ -78,22 +78,23 @@ class FiatAmountBar extends StatelessWidget {
78 borderRadius: BorderRadius.circular(99999),
79 onTap: onAllButtonPressed,
80 child: Padding(
81 - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
81 + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
82 child: Text(
83 - formatAmount(allAmount!),
83 + _formatAmount(allAmount!),
84 style: TextStyle(
85 - color: allAmountTextColor ?? Theme.of(context).colorScheme.primary),
85 + color: allAmountTextColor ?? Theme.of(context).colorScheme.primary,
86 + ),
87 ),
88 ),
88 - )),
89 - )
90 - ],
91 - )
92 - ],
93 - );
94 - }
89 + ),
90 + ),
91 + ),
92 + ],
93 + ),
94 + ],
95 + );
96
96 - String formatAmount(String amount) {
97 + String _formatAmount(String amount) {
98 try {
99 return double.parse(amount).toStringAsPrecision(8).replaceFirst(RegExp(r"\.?0+$"), "");
100 } catch (e) {
lib/new-ui/widgets/send_page/send_amount_input.dart
+91 -85
@@ -1,20 +1,20 @@
1 -import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
2 -import 'package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart';
3 -import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
4 -import 'package:cake_wallet/utils/decimal_input_formatter.dart';
5 -import 'package:flutter/material.dart';
6 -import 'package:flutter/services.dart';
7 -import 'package:flutter_mobx/flutter_mobx.dart';
1 +import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
2 +import "package:cake_wallet/new-ui/widgets/send_page/floating_icon_button.dart";
3 +import "package:cake_wallet/src/widgets/cake_image_widget.dart";
4 +import "package:cake_wallet/utils/decimal_input_formatter.dart";
5 +import "package:flutter/material.dart";
6 +import "package:flutter/services.dart";
7 +import "package:flutter_mobx/flutter_mobx.dart";
8
9 class NewSendAmountInput extends StatefulWidget {
10 const NewSendAmountInput({
11 - super.key,
11 required this.currency,
12 required this.maxDecimals,
13 required this.hasPicker,
14 required this.onPickerClicked,
15 required this.currencyIconPath,
16 required this.amountController,
17 + super.key,
18 this.validator,
19 });
20
@@ -41,27 +41,28 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
41 }
42
43 @override
44 - Widget build(BuildContext context) {
45 - return FormField<String>(
46 - key: formFieldKey,
47 - initialValue: widget.amountController.text,
48 - validator: widget.validator,
49 - builder: (state) => Column(
50 - crossAxisAlignment: CrossAxisAlignment.start,
51 - children: [
52 - Container(
44 + Widget build(BuildContext context) => FormField<String>(
45 + key: formFieldKey,
46 + initialValue: widget.amountController.text,
47 + validator: widget.validator,
48 + builder: (state) => Column(
49 + crossAxisAlignment: CrossAxisAlignment.start,
50 + children: [
51 + Container(
52 decoration: BoxDecoration(
54 - color: widget.hasPicker
55 - ? Theme.of(context).colorScheme.surfaceContainerHigh
56 - : Theme.of(context).colorScheme.surfaceContainer,
57 - borderRadius: BorderRadius.circular(18)),
53 + color: widget.hasPicker
54 + ? Theme.of(context).colorScheme.surfaceContainerHigh
55 + : Theme.of(context).colorScheme.surfaceContainer,
56 + borderRadius: BorderRadius.circular(18),
57 + ),
58 child: Row(
59 children: [
60 Expanded(
61 child: Container(
62 decoration: BoxDecoration(
63 - color: Theme.of(context).colorScheme.surfaceContainer,
64 - borderRadius: BorderRadius.horizontal(left: Radius.circular(18))),
63 + color: Theme.of(context).colorScheme.surfaceContainer,
64 + borderRadius: const BorderRadius.horizontal(left: Radius.circular(18)),
65 + ),
66 child: Row(
67 mainAxisSize: MainAxisSize.max,
68 spacing: 8,
@@ -79,82 +80,87 @@ class _NewSendAmountInputState extends State<NewSendAmountInput> {
80 ],
81 controller: widget.amountController,
82 decoration: InputDecoration(
82 - hintText: widget.maxDecimals > 0 ? "0" : "0.00",
83 + hintText: widget.maxDecimals == 0 ? "0" : "0.00",
84 errorMaxLines: 3,
85 ),
86 onChanged: state.didChange,
87 ),
88 ),
89 FloatingIconButton(
89 - iconPath: "assets/new-ui/paste.svg",
90 - onPressed: () async {
91 - final data = await Clipboard.getData(Clipboard.kTextPlain);
92 - if (data != null && data.text != null) {
93 - final text = data.text!;
94 - widget.amountController.value = TextEditingValue(
95 - text: text,
96 - selection: TextSelection.collapsed(offset: text.length),
97 - );
98 - }
99 - }),
90 + iconPath: "assets/new-ui/paste.svg",
91 + onPressed: () async {
92 + final data = await Clipboard.getData(Clipboard.kTextPlain);
93 + if (data != null && data.text != null) {
94 + final text = data.text!;
95 + widget.amountController.value = TextEditingValue(
96 + text: text,
97 + selection: TextSelection.collapsed(offset: text.length),
98 + );
99 + }
100 + },
101 + ),
102 ],
103 ),
104 ),
105 ),
106 IntrinsicWidth(
107 child: Observer(
106 - builder: (_) {
107 - return GestureDetector(
108 - onTap: widget.onPickerClicked,
109 - child: Container(
110 - alignment: Alignment.center,
111 - width: double.infinity,
112 - decoration: BoxDecoration(
113 - borderRadius: BorderRadius.only(
114 - topRight: Radius.circular(18),
115 - bottomRight: Radius.circular(18)),
116 - color: widget.hasPicker
117 - ? Theme.of(context).colorScheme.surfaceContainerHigh
118 - : Theme.of(context).colorScheme.surfaceContainer,
119 - ),
120 - child: Padding(
121 - padding: EdgeInsets.symmetric(horizontal: 12),
122 - child: Row(
123 - mainAxisAlignment: MainAxisAlignment.center,
124 - mainAxisSize: MainAxisSize.max,
125 - spacing: 8,
126 - children: [
127 - if (widget.hasPicker && widget.currencyIconPath.isNotEmpty)
128 - TokenImageWidget(
129 - imageUrl: widget.currencyIconPath, size: 24),
130 - Text(widget.currency),
131 - if (widget.hasPicker)
132 - CakeImageWidget(
133 - imageUrl: "assets/new-ui/chooser.svg",
134 - width: 12,
135 - height: 12,
136 - colorFilter: ColorFilter.mode(
137 - Theme.of(context).colorScheme.primary,
138 - BlendMode.srcIn),
139 - )
140 - ],
141 - ))),
142 - );
143 - },
108 + builder: (_) => GestureDetector(
109 + onTap: widget.onPickerClicked,
110 + child: Container(
111 + alignment: Alignment.center,
112 + width: double.infinity,
113 + decoration: BoxDecoration(
114 + borderRadius: const BorderRadius.only(
115 + topRight: Radius.circular(18),
116 + bottomRight: Radius.circular(18),
117 + ),
118 + color: widget.hasPicker
119 + ? Theme.of(context).colorScheme.surfaceContainerHigh
120 + : Theme.of(context).colorScheme.surfaceContainer,
121 + ),
122 + child: Padding(
123 + padding: const EdgeInsets.symmetric(horizontal: 12),
124 + child: Row(
125 + mainAxisAlignment: MainAxisAlignment.center,
126 + mainAxisSize: MainAxisSize.max,
127 + spacing: 8,
128 + children: [
129 + if (widget.hasPicker && widget.currencyIconPath.isNotEmpty)
130 + TokenImageWidget(
131 + imageUrl: widget.currencyIconPath,
132 + size: 24,
133 + ),
134 + Text(widget.currency),
135 + if (widget.hasPicker)
136 + CakeImageWidget(
137 + imageUrl: "assets/new-ui/chooser.svg",
138 + width: 12,
139 + height: 12,
140 + colorFilter: ColorFilter.mode(
141 + Theme.of(context).colorScheme.primary,
142 + BlendMode.srcIn,
143 + ),
144 + ),
145 + ],
146 + ),
147 + ),
148 + ),
149 + ),
150 ),
151 ),
152 ],
147 - )),
148 - if (state.hasError)
149 - Padding(
150 - padding: EdgeInsets.only(top: 6, left: 8),
151 - child: Text(
152 - state.errorText!,
153 - style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.error),
153 ),
155 - )
156 - ],
157 - ),
158 - );
159 - }
154 + ),
155 + if (state.hasError)
156 + Padding(
157 + padding: const EdgeInsets.only(top: 6, left: 8),
158 + child: Text(
159 + state.errorText!,
160 + style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.error),
161 + ),
162 + ),
163 + ],
164 + ),
165 + );
166 }