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
}