CAKE-345 | reworked send page for batch sending; added send_item.dart, send_card.dart, send_template_view_model.dart and parse_address_from_domain_alert.dart to the app; reworked send template page; fixed exchange_trade_page.dart and exchange_trade_view_model.dart
OleksandrSobol committed
Jul 16, 2021 at 12:32 UTC
e6cfa2b6365ca0d47da69b32e5a46245a98ed1a4
13 files changed
+1190
-877
lib/di.dart
+11
-2
@@ -72,6 +72,7 @@ import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.
72
import 'package:cake_wallet/view_model/order_details_view_model.dart';
73
import 'package:cake_wallet/view_model/rescan_view_model.dart';
74
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
75
+import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
76
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
77
import 'package:cake_wallet/view_model/support_view_model.dart';
78
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
@@ -307,10 +308,17 @@ Future setup(
308
addressEditOrCreateViewModel:
309
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
310
310
- getIt.registerFactory<SendViewModel>(() => SendViewModel(
311
+ getIt.registerFactory<SendTemplateViewModel>(() => SendTemplateViewModel(
312
getIt.get<AppStore>().wallet,
313
getIt.get<AppStore>().settingsStore,
314
getIt.get<SendTemplateStore>(),
315
+ getIt.get<FiatConversionStore>()
316
+ ));
317
+
318
+ getIt.registerFactory<SendViewModel>(() => SendViewModel(
319
+ getIt.get<AppStore>().wallet,
320
+ getIt.get<AppStore>().settingsStore,
321
+ getIt.get<SendTemplateViewModel>(),
322
getIt.get<FiatConversionStore>(),
323
_transactionDescriptionBox));
324
@@ -318,7 +326,8 @@ Future setup(
326
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
327
328
getIt.registerFactory(
321
- () => SendTemplatePage(sendViewModel: getIt.get<SendViewModel>()));
329
+ () => SendTemplatePage(
330
+ sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
331
332
getIt.registerFactory(() => WalletListViewModel(
333
_walletInfoSource,
lib/src/screens/exchange_trade/exchange_trade_page.dart
+4
-2
@@ -262,6 +262,9 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
262
263
if (state is ExecutedSuccessfullyState) {
264
WidgetsBinding.instance.addPostFrameCallback((_) {
265
+ final item = widget.exchangeTradeViewModel.sendViewModel
266
+ .sendItemList.first;
267
+
268
showPopUp<void>(
269
context: context,
270
builder: (BuildContext context) {
@@ -386,8 +389,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
389
' ' +
390
widget.exchangeTradeViewModel.sendViewModel.fiat.title,
391
recipientTitle: S.of(context).recipient_address,
389
- recipientAddress:
390
- widget.exchangeTradeViewModel.sendViewModel.address);
392
+ recipientAddress: item.address);
393
});
394
});
395
}
lib/src/screens/send/send_page.dart
+270
-632
@@ -1,17 +1,13 @@
1
import 'dart:ui';
2
-import 'package:cake_wallet/entities/monero_transaction_priority.dart';
3
-import 'package:cake_wallet/entities/transaction_priority.dart';
2
+import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
3
+import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
4
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
5
-import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
6
-import 'package:cake_wallet/src/widgets/picker.dart';
5
import 'package:cake_wallet/src/widgets/template_tile.dart';
8
-import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
6
+import 'package:cake_wallet/view_model/send/send_item.dart';
7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/material.dart';
11
-import 'package:flutter/services.dart';
9
import 'package:flutter_mobx/flutter_mobx.dart';
10
import 'package:mobx/mobx.dart';
14
-import 'package:keyboard_actions/keyboard_actions.dart';
11
import 'package:cake_wallet/routes.dart';
12
import 'package:cake_wallet/view_model/send/send_view_model.dart';
13
import 'package:cake_wallet/core/execution_state.dart';
@@ -21,42 +17,18 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
17
import 'package:cake_wallet/src/widgets/trail_button.dart';
18
import 'package:cake_wallet/utils/show_pop_up.dart';
19
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
24
-import 'package:cake_wallet/src/widgets/address_text_field.dart';
20
import 'package:cake_wallet/generated/i18n.dart';
21
import 'package:dotted_border/dotted_border.dart';
22
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
23
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
29
-import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
24
+import 'package:smooth_page_indicator/smooth_page_indicator.dart';
25
26
class SendPage extends BasePage {
32
- SendPage({@required this.sendViewModel})
33
- : _addressController = TextEditingController(),
34
- _cryptoAmountController = TextEditingController(),
35
- _fiatAmountController = TextEditingController(),
36
- _noteController = TextEditingController(),
37
- _formKey = GlobalKey<FormState>(),
38
- _cryptoAmountFocus = FocusNode(),
39
- _fiatAmountFocus = FocusNode(),
40
- _addressFocusNode = FocusNode() {
41
- _addressFocusNode.addListener(() {
42
- if (!_addressFocusNode.hasFocus && _addressController.text.isNotEmpty) {
43
- getOpenaliasRecord(_addressFocusNode.context);
44
- }
45
- });
46
- }
47
-
48
- static const prefixIconWidth = 34.0;
49
- static const prefixIconHeight = 34.0;
27
+ SendPage({@required this.sendViewModel}) :_formKey = GlobalKey<FormState>();
28
29
final SendViewModel sendViewModel;
52
- final TextEditingController _addressController;
53
- final TextEditingController _cryptoAmountController;
54
- final TextEditingController _fiatAmountController;
55
- final TextEditingController _noteController;
30
final GlobalKey<FormState> _formKey;
57
- final FocusNode _cryptoAmountFocus;
58
- final FocusNode _fiatAmountFocus;
59
- final FocusNode _addressFocusNode;
31
+ final controller = PageController(initialPage: 0);
32
33
bool _effectsInstalled = false;
34
@@ -76,505 +48,277 @@ class SendPage extends BasePage {
48
AppBarStyle get appBarStyle => AppBarStyle.transparent;
49
50
@override
79
- Widget trailing(context) => TrailButton(
80
- caption: S.of(context).clear,
81
- onPressed: () {
82
- _formKey.currentState.reset();
83
- sendViewModel.reset();
84
- });
51
+ Widget trailing(context) => Observer(builder: (_) {
52
+ return sendViewModel.isRemoveButtonShow
53
+ ? TrailButton(
54
+ caption: S.of(context).remove,
55
+ onPressed: () {
56
+ var pageToJump = controller.page.round() - 1;
57
+ pageToJump = pageToJump > 0 ? pageToJump : 0;
58
+ final item = _defineCurrentSendItem();
59
+ sendViewModel.removeSendItem(item);
60
+ controller.jumpToPage(pageToJump);
61
+ })
62
+ : TrailButton(
63
+ caption: S.of(context).clear,
64
+ onPressed: () {
65
+ final item = _defineCurrentSendItem();
66
+ _formKey.currentState.reset();
67
+ item.reset();
68
+ });
69
+ });
70
71
@override
72
Widget body(BuildContext context) {
73
_setEffects(context);
74
90
- return KeyboardActions(
91
- config: KeyboardActionsConfig(
92
- keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
93
- keyboardBarColor: Theme.of(context).accentTextTheme.body2
94
- .backgroundColor,
95
- nextFocus: false,
96
- actions: [
97
- KeyboardActionsItem(
98
- focusNode: _cryptoAmountFocus,
99
- toolbarButtons: [(_) => KeyboardDoneButton()],
75
+ return Form(
76
+ key: _formKey,
77
+ child: ScrollableWithBottomSection(
78
+ contentPadding: EdgeInsets.only(bottom: 24),
79
+ content: Column(
80
+ children: <Widget>[
81
+ Container(
82
+ height: 445,
83
+ child: Observer(
84
+ builder: (_) {
85
+ return PageView.builder(
86
+ scrollDirection: Axis.horizontal,
87
+ controller: controller,
88
+ itemCount: sendViewModel.sendItemList.length,
89
+ itemBuilder: (context, index) {
90
+ final item = sendViewModel.sendItemList[index];
91
+
92
+ return SendCard(
93
+ key: item.key,
94
+ item: item,
95
+ sendViewModel: sendViewModel,
96
+ );
97
+ }
98
+ );
99
+ },
100
+ )
101
),
101
- KeyboardActionsItem(
102
- focusNode: _fiatAmountFocus,
103
- toolbarButtons: [(_) => KeyboardDoneButton()],
104
- )
105
- ]),
106
- child: Container(
107
- height: 0,
108
- color: Theme.of(context).backgroundColor,
109
- child: ScrollableWithBottomSection(
110
- contentPadding: EdgeInsets.only(bottom: 24),
111
- content: Column(
112
- children: <Widget>[
113
- Container(
114
- decoration: BoxDecoration(
115
- borderRadius: BorderRadius.only(
116
- bottomLeft: Radius.circular(24),
117
- bottomRight: Radius.circular(24)),
118
- gradient: LinearGradient(colors: [
119
- Theme.of(context).primaryTextTheme.subhead.color,
120
- Theme.of(context)
121
- .primaryTextTheme
122
- .subhead
123
- .decorationColor,
124
- ], begin: Alignment.topLeft, end: Alignment.bottomRight),
125
- ),
126
- child: Form(
127
- key: _formKey,
128
- child: Column(children: <Widget>[
129
- Padding(
130
- padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
131
- child: Column(
132
- children: <Widget>[
133
- AddressTextField(
134
- focusNode: _addressFocusNode,
135
- controller: _addressController,
136
- onURIScanned: (uri) {
137
- var address = '';
138
- var amount = '';
139
-
140
- if (uri != null) {
141
- address = uri.path;
142
- amount = uri.queryParameters['tx_amount'] ??
143
- uri.queryParameters['amount'];
144
- } else {
145
- address = uri.toString();
146
- }
147
-
148
- _addressController.text = address;
149
- _cryptoAmountController.text = amount;
150
- },
151
- options: [
152
- AddressTextFieldOption.paste,
153
- AddressTextFieldOption.qrCode,
154
- AddressTextFieldOption.addressBook
155
- ],
156
- buttonColor: Theme.of(context)
102
+ Padding(
103
+ padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
104
+ child: Container(
105
+ height: 10,
106
+ child: Observer(builder: (_) {
107
+ final count = sendViewModel.sendItemList.length;
108
+
109
+ return count > 1
110
+ ? SmoothPageIndicator(
111
+ controller: controller,
112
+ count: count,
113
+ effect: ScrollingDotsEffect(
114
+ spacing: 6.0,
115
+ radius: 6.0,
116
+ dotWidth: 6.0,
117
+ dotHeight: 6.0,
118
+ dotColor: Theme.of(context)
119
.primaryTextTheme
158
- .display1
159
- .color,
160
- borderColor: Theme.of(context)
120
+ .display2
121
+ .backgroundColor,
122
+ activeDotColor: Theme.of(context)
123
.primaryTextTheme
162
- .headline
163
- .color,
164
- textStyle: TextStyle(
165
- fontSize: 14,
166
- fontWeight: FontWeight.w500,
167
- color: Colors.white),
168
- hintStyle: TextStyle(
169
- fontSize: 14,
170
- fontWeight: FontWeight.w500,
171
- color: Theme.of(context)
172
- .primaryTextTheme
173
- .headline
174
- .decorationColor),
175
- validator: sendViewModel.addressValidator,
176
- ),
177
- Observer(
178
- builder: (_) => Padding(
179
- padding: const EdgeInsets.only(top: 20),
180
- child: Stack(
181
- children: [
182
- BaseTextFormField(
183
- focusNode: _cryptoAmountFocus,
184
- controller: _cryptoAmountController,
185
- keyboardType:
186
- TextInputType.numberWithOptions(
187
- signed: false, decimal: true),
188
- prefixIcon: Padding(
189
- padding: EdgeInsets.only(top: 9),
190
- child: Text(
191
- sendViewModel.currency.title +
192
- ':',
193
- style: TextStyle(
194
- fontSize: 16,
195
- fontWeight: FontWeight.w600,
196
- color: Colors.white,
197
- )),
198
- ),
199
- suffixIcon: SizedBox(
200
- width: prefixIconWidth,
201
- ),
202
- hintText: '0.0000',
203
- borderColor: Theme.of(context)
204
- .primaryTextTheme
205
- .headline
206
- .color,
207
- textStyle: TextStyle(
208
- fontSize: 14,
209
- fontWeight: FontWeight.w500,
210
- color: Colors.white),
211
- placeholderTextStyle: TextStyle(
212
- color: Theme.of(context)
213
- .primaryTextTheme
214
- .headline
215
- .decorationColor,
216
- fontWeight: FontWeight.w500,
217
- fontSize: 14),
218
- validator: sendViewModel.sendAll
219
- ? sendViewModel.allAmountValidator
220
- : sendViewModel
221
- .amountValidator),
222
- Positioned(
223
- top: 2,
224
- right: 0,
225
- child: Container(
226
- width: prefixIconWidth,
227
- height: prefixIconHeight,
228
- child: InkWell(
229
- onTap: () async =>
230
- sendViewModel.setSendAll(),
231
- child: Container(
232
- decoration: BoxDecoration(
233
- color: Theme.of(context)
234
- .primaryTextTheme
235
- .display1
236
- .color,
237
- borderRadius:
238
- BorderRadius.all(
239
- Radius.circular(6))),
240
- child: Center(
241
- child: Text(
242
- S.of(context).all,
243
- textAlign:
244
- TextAlign.center,
245
- style: TextStyle(
246
- fontSize: 12,
247
- fontWeight:
248
- FontWeight.bold,
249
- color:
250
- Theme.of(context)
251
- .primaryTextTheme
252
- .display1
253
- .decorationColor))),
254
- ))))])
255
- )),
256
- Observer(
257
- builder: (_) => Padding(
258
- padding: EdgeInsets.only(top: 10),
259
- child: Row(
260
- mainAxisSize: MainAxisSize.max,
261
- mainAxisAlignment:
262
- MainAxisAlignment.spaceBetween,
263
- children: <Widget>[
264
- Expanded(
265
- child: Text(
266
- S.of(context).available_balance +
267
- ':',
268
- style: TextStyle(
269
- fontSize: 12,
270
- fontWeight: FontWeight.w600,
271
- color: Theme.of(context)
272
- .primaryTextTheme
273
- .headline
274
- .decorationColor),
275
- )),
276
- Text(
277
- sendViewModel.balance,
278
- style: TextStyle(
279
- fontSize: 12,
280
- fontWeight: FontWeight.w600,
281
- color: Theme.of(context)
282
- .primaryTextTheme
283
- .headline
284
- .decorationColor),
285
- )
286
- ],
287
- ),
288
- )),
289
- Padding(
290
- padding: const EdgeInsets.only(top: 20),
291
- child: BaseTextFormField(
292
- focusNode: _fiatAmountFocus,
293
- controller: _fiatAmountController,
294
- keyboardType:
295
- TextInputType.numberWithOptions(
296
- signed: false, decimal: true),
297
- prefixIcon: Padding(
298
- padding: EdgeInsets.only(top: 9),
299
- child:
300
- Text(sendViewModel.fiat.title + ':',
301
- style: TextStyle(
302
- fontSize: 16,
303
- fontWeight: FontWeight.w600,
304
- color: Colors.white,
305
- )),
306
- ),
307
- hintText: '0.00',
308
- borderColor: Theme.of(context)
309
- .primaryTextTheme
310
- .headline
311
- .color,
312
- textStyle: TextStyle(
313
- fontSize: 14,
314
- fontWeight: FontWeight.w500,
315
- color: Colors.white),
316
- placeholderTextStyle: TextStyle(
317
- color: Theme.of(context)
318
- .primaryTextTheme
319
- .headline
320
- .decorationColor,
321
- fontWeight: FontWeight.w500,
322
- fontSize: 14),
323
- )),
324
- Padding(
325
- padding: EdgeInsets.only(top: 20),
326
- child: BaseTextFormField(
327
- controller: _noteController,
328
- keyboardType: TextInputType.multiline,
329
- maxLines: null,
330
- borderColor: Theme.of(context)
331
- .primaryTextTheme
332
- .headline
333
- .color,
334
- textStyle: TextStyle(
335
- fontSize: 14,
336
- fontWeight: FontWeight.w500,
337
- color: Colors.white),
338
- hintText: S.of(context).note_optional,
339
- placeholderTextStyle: TextStyle(
340
- fontSize: 14,
341
- fontWeight: FontWeight.w500,
342
- color: Theme.of(context)
343
- .primaryTextTheme
344
- .headline
345
- .decorationColor),
346
- ),
347
- ),
348
- Observer(
349
- builder: (_) => GestureDetector(
350
- onTap: () =>
351
- _setTransactionPriority(context),
352
- child: Container(
353
- padding: EdgeInsets.only(top: 24),
354
- child: Row(
355
- mainAxisAlignment:
356
- MainAxisAlignment.spaceBetween,
357
- crossAxisAlignment: CrossAxisAlignment.start,
358
- children: <Widget>[
359
- Text(
360
- S
361
- .of(context)
362
- .send_estimated_fee,
363
- style: TextStyle(
364
- fontSize: 12,
365
- fontWeight:
366
- FontWeight.w500,
367
- //color: Theme.of(context).primaryTextTheme.display2.color,
368
- color: Colors.white)),
369
- Container(
370
- child: Row(
371
- crossAxisAlignment: CrossAxisAlignment.start,
372
- children: <Widget>[
373
- Column(
374
- mainAxisAlignment: MainAxisAlignment.start,
375
- crossAxisAlignment: CrossAxisAlignment.end,
376
- children: [
377
- Text(
378
- sendViewModel
379
- .estimatedFee
380
- .toString() +
381
- ' ' +
382
- sendViewModel
383
- .currency.title,
384
- style: TextStyle(
385
- fontSize: 12,
386
- fontWeight:
387
- FontWeight.w600,
388
- //color: Theme.of(context).primaryTextTheme.display2.color,
389
- color:
390
- Colors.white)),
391
- Padding(
392
- padding:
393
- EdgeInsets.only(top: 5),
394
- child: Text(
395
- sendViewModel
396
- .estimatedFeeFiatAmount
397
- + ' ' +
398
- sendViewModel
399
- .fiat.title,
400
- style: TextStyle(
401
- fontSize: 12,
402
- fontWeight:
403
- FontWeight.w600,
404
- color: Theme
405
- .of(context)
406
- .primaryTextTheme
407
- .headline
408
- .decorationColor))
409
- ),
410
- ],
411
- ),
412
- Padding(
413
- padding: EdgeInsets.only(
414
- top: 2,
415
- left: 5),
416
- child: Icon(
417
- Icons.arrow_forward_ios,
418
- size: 12,
419
- color: Colors.white,
420
- ),
421
- )
422
- ],
423
- ),
424
- )
425
- ],
426
- ),
427
- ),
428
- ))
429
- ],
430
- ),
431
- )
432
- ]),
433
- ),
434
- ),
435
- Padding(
436
- padding: EdgeInsets.only(top: 30, left: 24, bottom: 24),
437
- child: Row(
438
- mainAxisAlignment: MainAxisAlignment.start,
439
- children: <Widget>[
440
- Text(
441
- S.of(context).send_templates,
442
- style: TextStyle(
443
- fontSize: 18,
444
- fontWeight: FontWeight.w600,
124
+ .display3
125
+ .backgroundColor
126
+ )
127
+ )
128
+ : Offstage();
129
+ })
130
+ )
131
+ ),
132
+ Padding(
133
+ padding: EdgeInsets.only(left: 24, bottom: 24),
134
+ child: Row(
135
+ mainAxisAlignment: MainAxisAlignment.start,
136
+ children: <Widget>[
137
+ Text(
138
+ S.of(context).send_templates,
139
+ style: TextStyle(
140
+ fontSize: 18,
141
+ fontWeight: FontWeight.w600,
142
+ color: Theme.of(context)
143
+ .primaryTextTheme
144
+ .display4
145
+ .color),
146
+ )
147
+ ],
148
+ ),
149
+ ),
150
+ Container(
151
+ height: 40,
152
+ width: double.infinity,
153
+ padding: EdgeInsets.only(left: 24),
154
+ child: SingleChildScrollView(
155
+ scrollDirection: Axis.horizontal,
156
+ child: Row(
157
+ children: <Widget>[
158
+ GestureDetector(
159
+ onTap: () => Navigator.of(context)
160
+ .pushNamed(Routes.sendTemplate),
161
+ child: Container(
162
+ padding: EdgeInsets.only(left: 1, right: 10),
163
+ child: DottedBorder(
164
+ borderType: BorderType.RRect,
165
+ dashPattern: [6, 4],
166
color: Theme.of(context)
167
.primaryTextTheme
447
- .display4
448
- .color),
449
- )
450
- ],
451
- ),
452
- ),
453
- Container(
454
- height: 40,
455
- width: double.infinity,
456
- padding: EdgeInsets.only(left: 24),
457
- child: SingleChildScrollView(
458
- scrollDirection: Axis.horizontal,
459
- child: Row(
460
- children: <Widget>[
461
- GestureDetector(
462
- onTap: () => Navigator.of(context)
463
- .pushNamed(Routes.sendTemplate),
464
- child: Container(
465
- padding: EdgeInsets.only(left: 1, right: 10),
466
- child: DottedBorder(
467
- borderType: BorderType.RRect,
468
- dashPattern: [6, 4],
469
- color: Theme.of(context)
470
- .primaryTextTheme
471
- .display2
472
- .decorationColor,
473
- strokeWidth: 2,
474
- radius: Radius.circular(20),
475
- child: Container(
476
- height: 34,
477
- width: 75,
478
- padding:
479
- EdgeInsets.only(left: 10, right: 10),
480
- alignment: Alignment.center,
481
- decoration: BoxDecoration(
482
- borderRadius:
483
- BorderRadius.all(Radius.circular(20)),
484
- color: Colors.transparent,
485
- ),
486
- child: Text(
487
- S.of(context).send_new,
488
- style: TextStyle(
489
- fontSize: 14,
490
- fontWeight: FontWeight.w600,
491
- color: Theme.of(context)
492
- .primaryTextTheme
493
- .display3
494
- .color),
495
- ),
496
- )),
497
- ),
498
- ),
499
- Observer(builder: (_) {
500
- final templates = sendViewModel.templates;
501
- final itemCount = templates.length;
502
-
503
- return ListView.builder(
504
- scrollDirection: Axis.horizontal,
505
- shrinkWrap: true,
506
- physics: NeverScrollableScrollPhysics(),
507
- itemCount: itemCount,
508
- itemBuilder: (context, index) {
509
- final template = templates[index];
510
-
511
- return TemplateTile(
512
- key: UniqueKey(),
513
- to: template.name,
514
- amount: template.amount,
515
- from: template.cryptoCurrency,
516
- onTap: () {
517
- _addressController.text =
518
- template.address;
519
- _cryptoAmountController.text =
520
- template.amount;
521
- getOpenaliasRecord(context);
522
- },
523
- onRemove: () {
524
- showPopUp<void>(
525
- context: context,
526
- builder: (dialogContext) {
527
- return AlertWithTwoActions(
528
- alertTitle:
529
- S.of(context).template,
530
- alertContent: S
531
- .of(context)
532
- .confirm_delete_template,
533
- rightButtonText:
534
- S.of(context).delete,
535
- leftButtonText:
536
- S.of(context).cancel,
537
- actionRightButton: () {
538
- Navigator.of(dialogContext)
539
- .pop();
540
- sendViewModel.removeTemplate(
541
- template: template);
542
- sendViewModel
543
- .updateTemplate();
544
- },
545
- actionLeftButton: () =>
546
- Navigator.of(dialogContext)
547
- .pop());
548
- });
549
- },
550
- );
551
- });
552
- })
553
- ],
168
+ .display2
169
+ .decorationColor,
170
+ strokeWidth: 2,
171
+ radius: Radius.circular(20),
172
+ child: Container(
173
+ height: 34,
174
+ width: 75,
175
+ padding:
176
+ EdgeInsets.only(left: 10, right: 10),
177
+ alignment: Alignment.center,
178
+ decoration: BoxDecoration(
179
+ borderRadius:
180
+ BorderRadius.all(Radius.circular(20)),
181
+ color: Colors.transparent,
182
+ ),
183
+ child: Text(
184
+ S.of(context).send_new,
185
+ style: TextStyle(
186
+ fontSize: 14,
187
+ fontWeight: FontWeight.w600,
188
+ color: Theme.of(context)
189
+ .primaryTextTheme
190
+ .display3
191
+ .color),
192
+ ),
193
+ )),
194
+ ),
195
),
555
- ),
556
- )
557
- ],
196
+ Observer(builder: (_) {
197
+ final templates = sendViewModel.templates;
198
+ final itemCount = templates.length;
199
+
200
+ return ListView.builder(
201
+ scrollDirection: Axis.horizontal,
202
+ shrinkWrap: true,
203
+ physics: NeverScrollableScrollPhysics(),
204
+ itemCount: itemCount,
205
+ itemBuilder: (context, index) {
206
+ final template = templates[index];
207
+
208
+ return TemplateTile(
209
+ key: UniqueKey(),
210
+ to: template.name,
211
+ amount: template.amount,
212
+ from: template.cryptoCurrency,
213
+ onTap: () async {
214
+ final item = _defineCurrentSendItem();
215
+ item.address =
216
+ template.address;
217
+ item.setCryptoAmount(template.amount);
218
+ final record =
219
+ await item.getOpenaliasRecord();
220
+
221
+ if (record != null) {
222
+ showAddressAlert(
223
+ context,
224
+ S.current.openalias_alert_title,
225
+ S.current
226
+ .openalias_alert_content(record.name));
227
+ }
228
+ },
229
+ onRemove: () {
230
+ showPopUp<void>(
231
+ context: context,
232
+ builder: (dialogContext) {
233
+ return AlertWithTwoActions(
234
+ alertTitle:
235
+ S.of(context).template,
236
+ alertContent: S
237
+ .of(context)
238
+ .confirm_delete_template,
239
+ rightButtonText:
240
+ S.of(context).delete,
241
+ leftButtonText:
242
+ S.of(context).cancel,
243
+ actionRightButton: () {
244
+ Navigator.of(dialogContext)
245
+ .pop();
246
+ sendViewModel
247
+ .sendTemplateViewModel
248
+ .removeTemplate(
249
+ template: template);
250
+ },
251
+ actionLeftButton: () =>
252
+ Navigator.of(dialogContext)
253
+ .pop());
254
+ });
255
+ },
256
+ );
257
+ });
258
+ })
259
+ ],
260
+ ),
261
+ ),
262
+ )
263
+ ],
264
+ ),
265
+ bottomSectionPadding:
266
+ EdgeInsets.only(left: 24, right: 24, bottom: 24),
267
+ bottomSection: Column(
268
+ children: [
269
+ PrimaryButton(
270
+ onPressed: () {
271
+ sendViewModel.addSendItem();
272
+ },
273
+ text: 'Add receiver',
274
+ color: Colors.green,
275
+ textColor: Colors.white,
276
),
559
- bottomSectionPadding:
560
- EdgeInsets.only(left: 24, right: 24, bottom: 24),
561
- bottomSection: Observer(builder: (_) {
562
- return LoadingPrimaryButton(
563
- onPressed: () async {
564
- if (_formKey.currentState.validate()) {
565
- await sendViewModel.createTransaction();
566
- }
567
- },
568
- text: S.of(context).send,
569
- color: Theme.of(context).accentTextTheme.body2.color,
570
- textColor: Colors.white,
571
- isLoading: sendViewModel.state is IsExecutingState ||
572
- sendViewModel.state is TransactionCommitting,
573
- isDisabled:
574
- false // FIXME !(syncStore.status is SyncedSyncStatus),
277
+ Padding(
278
+ padding: EdgeInsets.only(top: 12),
279
+ child: Observer(builder: (_) {
280
+ return LoadingPrimaryButton(
281
+ onPressed: () async {
282
+ if (_formKey.currentState.validate()) {
283
+ //await sendViewModel.createTransaction();
284
+ // FIXME: for test only
285
+ sendViewModel.clearSendItemList();
286
+ await showPopUp<void>(
287
+ context: context,
288
+ builder: (BuildContext context) {
289
+ return AlertWithOneAction(
290
+ alertTitle: S.of(context).send,
291
+ alertContent: S.of(context).send_success(
292
+ sendViewModel.currency
293
+ .toString()),
294
+ buttonText: S.of(context).ok,
295
+ buttonAction: () => Navigator.of(context).pop());
296
+ });
297
+ } else {
298
+ await showPopUp<void>(
299
+ context: context,
300
+ builder: (BuildContext context) {
301
+ return AlertWithOneAction(
302
+ alertTitle: S.of(context).error,
303
+ alertContent: 'Please, check your receivers forms',
304
+ buttonText: S.of(context).ok,
305
+ buttonAction: () =>
306
+ Navigator.of(context).pop());
307
+ });
308
+ }
309
+ },
310
+ text: S.of(context).send,
311
+ color: Theme.of(context).accentTextTheme.body2.color,
312
+ textColor: Colors.white,
313
+ isLoading: sendViewModel.state is IsExecutingState ||
314
+ sendViewModel.state is TransactionCommitting,
315
+ isDisabled: !sendViewModel.isReadyForSend,
316
);
576
- })),
577
- ));
317
+ },
318
+ ))
319
+ ],
320
+ )),
321
+ );
322
}
323
324
void _setEffects(BuildContext context) {
@@ -582,78 +326,6 @@ class SendPage extends BasePage {
326
return;
327
}
328
585
- _cryptoAmountController.addListener(() {
586
- final amount = _cryptoAmountController.text;
587
-
588
- if (sendViewModel.sendAll && amount != S.current.all) {
589
- sendViewModel.sendAll = false;
590
- }
591
-
592
- if (amount != sendViewModel.cryptoAmount) {
593
- sendViewModel.setCryptoAmount(amount);
594
- }
595
- });
596
-
597
- _fiatAmountController.addListener(() {
598
- final amount = _fiatAmountController.text;
599
-
600
- if (amount != sendViewModel.fiatAmount) {
601
- sendViewModel.sendAll = false;
602
- sendViewModel.setFiatAmount(amount);
603
- }
604
- });
605
-
606
- _noteController.addListener(() {
607
- final note = _noteController.text ?? '';
608
-
609
- if (note != sendViewModel.note) {
610
- sendViewModel.note = note;
611
- }
612
- });
613
-
614
- reaction((_) => sendViewModel.sendAll, (bool all) {
615
- if (all) {
616
- _cryptoAmountController.text = S.current.all;
617
- _fiatAmountController.text = null;
618
- }
619
- });
620
-
621
- reaction((_) => sendViewModel.fiatAmount, (String amount) {
622
- if (amount != _fiatAmountController.text) {
623
- _fiatAmountController.text = amount;
624
- }
625
- });
626
-
627
- reaction((_) => sendViewModel.cryptoAmount, (String amount) {
628
- if (sendViewModel.sendAll && amount != S.current.all) {
629
- sendViewModel.sendAll = false;
630
- }
631
-
632
- if (amount != _cryptoAmountController.text) {
633
- _cryptoAmountController.text = amount;
634
- }
635
- });
636
-
637
- reaction((_) => sendViewModel.address, (String address) {
638
- if (address != _addressController.text) {
639
- _addressController.text = address;
640
- }
641
- });
642
-
643
- _addressController.addListener(() {
644
- final address = _addressController.text;
645
-
646
- if (sendViewModel.address != address) {
647
- sendViewModel.address = address;
648
- }
649
- });
650
-
651
- reaction((_) => sendViewModel.note, (String note) {
652
- if (note != _noteController.text) {
653
- _noteController.text = note;
654
- }
655
- });
656
-
329
reaction((_) => sendViewModel.state, (ExecutionState state) {
330
if (state is FailureState) {
331
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -678,7 +350,7 @@ class SendPage extends BasePage {
350
alertTitle: S.of(context).confirm_sending,
351
amount: S.of(context).send_amount,
352
amountValue:
681
- sendViewModel.pendingTransaction.amountFormatted,
353
+ sendViewModel.pendingTransaction.amountFormatted,
354
fiatAmountValue: sendViewModel.pendingTransactionFiatAmount
355
+ ' ' + sendViewModel.fiat.title,
356
fee: S.of(context).send_fee,
@@ -686,7 +358,7 @@ class SendPage extends BasePage {
358
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
359
+ ' ' + sendViewModel.fiat.title,
360
recipientTitle: S.of(context).recipient_address,
689
- recipientAddress: sendViewModel.address,
361
+ recipientAddress: '', // FIXME: sendViewModel.address,
362
rightButtonText: S.of(context).ok,
363
leftButtonText: S.of(context).cancel,
364
actionRightButton: () {
@@ -724,8 +396,7 @@ class SendPage extends BasePage {
396
397
if (state is TransactionCommitted) {
398
WidgetsBinding.instance.addPostFrameCallback((_) {
727
- _addressController.text = '';
728
- _cryptoAmountController.text = '';
399
+ sendViewModel.clearSendItemList();
400
});
401
}
402
});
@@ -733,41 +404,8 @@ class SendPage extends BasePage {
404
_effectsInstalled = true;
405
}
406
736
- Future<void> getOpenaliasRecord(BuildContext context) async {
737
- final record =
738
- await sendViewModel.decodeOpenaliasRecord(_addressController.text);
739
-
740
- if (record != null) {
741
- _addressController.text = record.address;
742
-
743
- await showPopUp<void>(
744
- context: context,
745
- builder: (BuildContext context) {
746
- return AlertWithOneAction(
747
- alertTitle: S.of(context).openalias_alert_title,
748
- alertContent:
749
- S.of(context).openalias_alert_content(record.name),
750
- buttonText: S.of(context).ok,
751
- buttonAction: () => Navigator.of(context).pop());
752
- });
753
- }
754
- }
755
-
756
- Future<void> _setTransactionPriority(BuildContext context) async {
757
- final items = priorityForWalletType(sendViewModel.walletType);
758
- final selectedItem = items.indexOf(sendViewModel.transactionPriority);
759
- final isShowScrollThumb = items.length > 3;
760
-
761
- await showPopUp<void>(
762
- builder: (_) => Picker(
763
- items: items,
764
- displayItem: sendViewModel.displayFeeRate,
765
- selectedAtIndex: selectedItem,
766
- title: S.of(context).please_select,
767
- mainAxisAlignment: MainAxisAlignment.center,
768
- onItemSelected: (TransactionPriority priority) =>
769
- sendViewModel.setTransactionPriority(priority),
770
- ),
771
- context: context);
407
+ SendItem _defineCurrentSendItem() {
408
+ final itemCount = controller.page.round();
409
+ return sendViewModel.sendItemList[itemCount];
410
}
411
}
lib/src/screens/send/send_template_page.dart
+62
-59
@@ -2,11 +2,10 @@ import 'package:mobx/mobx.dart';
2
import 'package:flutter/cupertino.dart';
3
import 'package:flutter/material.dart';
4
import 'package:flutter/services.dart';
5
-import 'package:flutter_mobx/flutter_mobx.dart';
5
import 'package:keyboard_actions/keyboard_actions.dart';
6
import 'package:cake_wallet/src/screens/base_page.dart';
7
import 'package:cake_wallet/generated/i18n.dart';
9
-import 'package:cake_wallet/view_model/send/send_view_model.dart';
8
+import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
9
import 'package:cake_wallet/src/widgets/address_text_field.dart';
10
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
11
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
@@ -14,9 +13,11 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
13
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
14
15
class SendTemplatePage extends BasePage {
17
- SendTemplatePage({@required this.sendViewModel});
16
+ SendTemplatePage({@required this.sendTemplateViewModel}) {
17
+ sendTemplateViewModel.sendItem.reset();
18
+ }
19
19
- final SendViewModel sendViewModel;
20
+ final SendTemplateViewModel sendTemplateViewModel;
21
final _addressController = TextEditingController();
22
final _cryptoAmountController = TextEditingController();
23
final _fiatAmountController = TextEditingController();
@@ -100,7 +101,7 @@ class SendTemplatePage extends BasePage {
101
.decorationColor,
102
fontWeight: FontWeight.w500,
103
fontSize: 14),
103
- validator: sendViewModel.templateValidator,
104
+ validator: sendTemplateViewModel.templateValidator,
105
),
106
Padding(
107
padding: EdgeInsets.only(top: 20),
@@ -145,49 +146,49 @@ class SendTemplatePage extends BasePage {
146
.primaryTextTheme
147
.headline
148
.decorationColor),
148
- validator: sendViewModel.addressValidator,
149
+ validator: sendTemplateViewModel.addressValidator,
150
),
151
),
151
- Observer(builder: (_) {
152
- return Padding(
153
- padding: const EdgeInsets.only(top: 20),
154
- child: BaseTextFormField(
155
- focusNode: _cryptoAmountFocus,
156
- controller: _cryptoAmountController,
157
- keyboardType: TextInputType.numberWithOptions(
158
- signed: false, decimal: true),
159
- inputFormatters: [
160
- BlacklistingTextInputFormatter(
161
- RegExp('[\\-|\\ ]'))
162
- ],
163
- prefixIcon: Padding(
164
- padding: EdgeInsets.only(top: 9),
165
- child:
166
- Text(sendViewModel.currency.title + ':',
167
- style: TextStyle(
168
- fontSize: 16,
169
- fontWeight: FontWeight.w600,
170
- color: Colors.white,
171
- )),
172
- ),
173
- hintText: '0.0000',
174
- borderColor: Theme.of(context)
175
- .primaryTextTheme
176
- .headline
177
- .color,
178
- textStyle: TextStyle(
179
- fontSize: 14,
180
- fontWeight: FontWeight.w500,
181
- color: Colors.white),
182
- placeholderTextStyle: TextStyle(
183
- color: Theme.of(context)
184
- .primaryTextTheme
185
- .headline
186
- .decorationColor,
187
- fontWeight: FontWeight.w500,
188
- fontSize: 14),
189
- validator: sendViewModel.amountValidator));
190
- }),
152
+ Padding(
153
+ padding: const EdgeInsets.only(top: 20),
154
+ child: BaseTextFormField(
155
+ focusNode: _cryptoAmountFocus,
156
+ controller: _cryptoAmountController,
157
+ keyboardType: TextInputType.numberWithOptions(
158
+ signed: false, decimal: true),
159
+ inputFormatters: [
160
+ BlacklistingTextInputFormatter(
161
+ RegExp('[\\-|\\ ]'))
162
+ ],
163
+ prefixIcon: Padding(
164
+ padding: EdgeInsets.only(top: 9),
165
+ child:
166
+ Text(sendTemplateViewModel
167
+ .currency.title + ':',
168
+ style: TextStyle(
169
+ fontSize: 16,
170
+ fontWeight: FontWeight.w600,
171
+ color: Colors.white,
172
+ )),
173
+ ),
174
+ hintText: '0.0000',
175
+ borderColor: Theme.of(context)
176
+ .primaryTextTheme
177
+ .headline
178
+ .color,
179
+ textStyle: TextStyle(
180
+ fontSize: 14,
181
+ fontWeight: FontWeight.w500,
182
+ color: Colors.white),
183
+ placeholderTextStyle: TextStyle(
184
+ color: Theme.of(context)
185
+ .primaryTextTheme
186
+ .headline
187
+ .decorationColor,
188
+ fontWeight: FontWeight.w500,
189
+ fontSize: 14),
190
+ validator: sendTemplateViewModel
191
+ .amountValidator)),
192
Padding(
193
padding: const EdgeInsets.only(top: 20),
194
child: BaseTextFormField(
@@ -201,7 +202,8 @@ class SendTemplatePage extends BasePage {
202
],
203
prefixIcon: Padding(
204
padding: EdgeInsets.only(top: 9),
204
- child: Text(sendViewModel.fiat.title + ':',
205
+ child: Text(sendTemplateViewModel
206
+ .fiat.title + ':',
207
style: TextStyle(
208
fontSize: 16,
209
fontWeight: FontWeight.w600,
@@ -236,12 +238,11 @@ class SendTemplatePage extends BasePage {
238
bottomSection: PrimaryButton(
239
onPressed: () {
240
if (_formKey.currentState.validate()) {
239
- sendViewModel.addTemplate(
241
+ sendTemplateViewModel.addTemplate(
242
name: _nameController.text,
243
address: _addressController.text,
242
- cryptoCurrency: sendViewModel.currency.title,
244
+ cryptoCurrency: sendTemplateViewModel.currency.title,
245
amount: _cryptoAmountController.text);
244
- sendViewModel.updateTemplate();
246
Navigator.of(context).pop();
247
}
248
},
@@ -258,19 +259,21 @@ class SendTemplatePage extends BasePage {
259
return;
260
}
261
261
- reaction((_) => sendViewModel.fiatAmount, (String amount) {
262
+ final item = sendTemplateViewModel.sendItem;
263
+
264
+ reaction((_) => item.fiatAmount, (String amount) {
265
if (amount != _fiatAmountController.text) {
266
_fiatAmountController.text = amount;
267
}
268
});
269
267
- reaction((_) => sendViewModel.cryptoAmount, (String amount) {
270
+ reaction((_) => item.cryptoAmount, (String amount) {
271
if (amount != _cryptoAmountController.text) {
272
_cryptoAmountController.text = amount;
273
}
274
});
275
273
- reaction((_) => sendViewModel.address, (String address) {
276
+ reaction((_) => item.address, (String address) {
277
if (address != _addressController.text) {
278
_addressController.text = address;
279
}
@@ -279,24 +282,24 @@ class SendTemplatePage extends BasePage {
282
_cryptoAmountController.addListener(() {
283
final amount = _cryptoAmountController.text;
284
282
- if (amount != sendViewModel.cryptoAmount) {
283
- sendViewModel.setCryptoAmount(amount);
285
+ if (amount != item.cryptoAmount) {
286
+ item.setCryptoAmount(amount);
287
}
288
});
289
290
_fiatAmountController.addListener(() {
291
final amount = _fiatAmountController.text;
292
290
- if (amount != sendViewModel.fiatAmount) {
291
- sendViewModel.setFiatAmount(amount);
293
+ if (amount != item.fiatAmount) {
294
+ item.setFiatAmount(amount);
295
}
296
});
297
298
_addressController.addListener(() {
299
final address = _addressController.text;
300
298
- if (sendViewModel.address != address) {
299
- sendViewModel.address = address;
301
+ if (item.address != address) {
302
+ item.address = address;
303
}
304
});
305
lib/src/screens/send/widgets/parse_address_from_domain_alert.dart
new
+17
@@ -0,0 +1,17 @@
1
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
2
+import 'package:cake_wallet/utils/show_pop_up.dart';
3
+import 'package:flutter/material.dart';
4
+import 'package:cake_wallet/generated/i18n.dart';
5
+
6
+void showAddressAlert(BuildContext context, String title, String content) async {
7
+ await showPopUp<void>(
8
+ context: context,
9
+ builder: (BuildContext context) {
10
+
11
+ return AlertWithOneAction(
12
+ alertTitle: title,
13
+ alertContent: content,
14
+ buttonText: S.of(context).ok,
15
+ buttonAction: () => Navigator.of(context).pop());
16
+ });
17
+}
\ No newline at end of file
lib/src/screens/send/widgets/send_card.dart
new
+521
@@ -0,0 +1,521 @@
1
+import 'dart:ui';
2
+import 'package:cake_wallet/entities/transaction_priority.dart';
3
+import 'package:cake_wallet/src/screens/send/widgets/parse_address_from_domain_alert.dart';
4
+import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
5
+import 'package:cake_wallet/src/widgets/picker.dart';
6
+import 'package:cake_wallet/view_model/send/send_item.dart';
7
+import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
8
+import 'package:flutter/cupertino.dart';
9
+import 'package:flutter/material.dart';
10
+import 'package:flutter/services.dart';
11
+import 'package:flutter_mobx/flutter_mobx.dart';
12
+import 'package:mobx/mobx.dart';
13
+import 'package:keyboard_actions/keyboard_actions.dart';
14
+import 'package:cake_wallet/view_model/send/send_view_model.dart';
15
+import 'package:cake_wallet/utils/show_pop_up.dart';
16
+import 'package:cake_wallet/src/widgets/address_text_field.dart';
17
+import 'package:cake_wallet/generated/i18n.dart';
18
+import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
19
+
20
+class SendCard extends StatefulWidget {
21
+ SendCard({Key key, @required this.item, @required this.sendViewModel}) : super(key: key);
22
+
23
+ final SendItem item;
24
+ final SendViewModel sendViewModel;
25
+
26
+ @override
27
+ SendCardState createState() => SendCardState(
28
+ item: item,
29
+ sendViewModel: sendViewModel
30
+ );
31
+}
32
+
33
+class SendCardState extends State<SendCard>
34
+ with AutomaticKeepAliveClientMixin<SendCard> {
35
+ SendCardState({@required this.item, @required this.sendViewModel})
36
+ : addressController = TextEditingController(),
37
+ cryptoAmountController = TextEditingController(),
38
+ fiatAmountController = TextEditingController(),
39
+ noteController = TextEditingController(),
40
+ cryptoAmountFocus = FocusNode(),
41
+ fiatAmountFocus = FocusNode(),
42
+ addressFocusNode = FocusNode();
43
+
44
+ static const prefixIconWidth = 34.0;
45
+ static const prefixIconHeight = 34.0;
46
+
47
+ final SendItem item;
48
+ final SendViewModel sendViewModel;
49
+
50
+ final TextEditingController addressController;
51
+ final TextEditingController cryptoAmountController;
52
+ final TextEditingController fiatAmountController;
53
+ final TextEditingController noteController;
54
+ final FocusNode cryptoAmountFocus;
55
+ final FocusNode fiatAmountFocus;
56
+ final FocusNode addressFocusNode;
57
+
58
+ bool _effectsInstalled = false;
59
+
60
+ @override
61
+ Widget build(BuildContext context) {
62
+ super.build(context);
63
+ _setEffects(context);
64
+
65
+ return KeyboardActions(
66
+ config: KeyboardActionsConfig(
67
+ keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
68
+ keyboardBarColor: Theme.of(context).accentTextTheme.body2
69
+ .backgroundColor,
70
+ nextFocus: false,
71
+ actions: [
72
+ KeyboardActionsItem(
73
+ focusNode: cryptoAmountFocus,
74
+ toolbarButtons: [(_) => KeyboardDoneButton()],
75
+ ),
76
+ KeyboardActionsItem(
77
+ focusNode: fiatAmountFocus,
78
+ toolbarButtons: [(_) => KeyboardDoneButton()],
79
+ )
80
+ ]),
81
+ child: Container(
82
+ height: 445,
83
+ decoration: BoxDecoration(
84
+ borderRadius: BorderRadius.only(
85
+ bottomLeft: Radius.circular(24),
86
+ bottomRight: Radius.circular(24)),
87
+ gradient: LinearGradient(colors: [
88
+ Theme.of(context).primaryTextTheme.subhead.color,
89
+ Theme.of(context)
90
+ .primaryTextTheme
91
+ .subhead
92
+ .decorationColor,
93
+ ], begin: Alignment.topLeft, end: Alignment.bottomRight),
94
+ ),
95
+ child: Padding(
96
+ padding: EdgeInsets.fromLTRB(24, 80, 24, 32),
97
+ child: SingleChildScrollView(
98
+ child: Column(
99
+ mainAxisSize: MainAxisSize.min,
100
+ children: <Widget>[
101
+ AddressTextField(
102
+ focusNode: addressFocusNode,
103
+ controller: addressController,
104
+ onURIScanned: (uri) {
105
+ var address = '';
106
+ var amount = '';
107
+
108
+ if (uri != null) {
109
+ address = uri.path;
110
+ amount = uri.queryParameters['tx_amount'] ??
111
+ uri.queryParameters['amount'];
112
+ } else {
113
+ address = uri.toString();
114
+ }
115
+
116
+ addressController.text = address;
117
+ cryptoAmountController.text = amount;
118
+ },
119
+ options: [
120
+ AddressTextFieldOption.paste,
121
+ AddressTextFieldOption.qrCode,
122
+ AddressTextFieldOption.addressBook
123
+ ],
124
+ buttonColor: Theme.of(context)
125
+ .primaryTextTheme
126
+ .display1
127
+ .color,
128
+ borderColor: Theme.of(context)
129
+ .primaryTextTheme
130
+ .headline
131
+ .color,
132
+ textStyle: TextStyle(
133
+ fontSize: 14,
134
+ fontWeight: FontWeight.w500,
135
+ color: Colors.white),
136
+ hintStyle: TextStyle(
137
+ fontSize: 14,
138
+ fontWeight: FontWeight.w500,
139
+ color: Theme.of(context)
140
+ .primaryTextTheme
141
+ .headline
142
+ .decorationColor),
143
+ validator: sendViewModel.addressValidator,
144
+ ),
145
+ Observer(
146
+ builder: (_) => Padding(
147
+ padding: const EdgeInsets.only(top: 20),
148
+ child: Stack(
149
+ children: [
150
+ BaseTextFormField(
151
+ focusNode: cryptoAmountFocus,
152
+ controller: cryptoAmountController,
153
+ keyboardType:
154
+ TextInputType.numberWithOptions(
155
+ signed: false, decimal: true),
156
+ prefixIcon: Padding(
157
+ padding: EdgeInsets.only(top: 9),
158
+ child: Text(
159
+ sendViewModel.currency.title +
160
+ ':',
161
+ style: TextStyle(
162
+ fontSize: 16,
163
+ fontWeight: FontWeight.w600,
164
+ color: Colors.white,
165
+ )),
166
+ ),
167
+ suffixIcon: SizedBox(
168
+ width: prefixIconWidth,
169
+ ),
170
+ hintText: '0.0000',
171
+ borderColor: Theme.of(context)
172
+ .primaryTextTheme
173
+ .headline
174
+ .color,
175
+ textStyle: TextStyle(
176
+ fontSize: 14,
177
+ fontWeight: FontWeight.w500,
178
+ color: Colors.white),
179
+ placeholderTextStyle: TextStyle(
180
+ color: Theme.of(context)
181
+ .primaryTextTheme
182
+ .headline
183
+ .decorationColor,
184
+ fontWeight: FontWeight.w500,
185
+ fontSize: 14),
186
+ validator: item.sendAll
187
+ ? sendViewModel.allAmountValidator
188
+ : sendViewModel
189
+ .amountValidator),
190
+ Positioned(
191
+ top: 2,
192
+ right: 0,
193
+ child: Container(
194
+ width: prefixIconWidth,
195
+ height: prefixIconHeight,
196
+ child: InkWell(
197
+ onTap: () async =>
198
+ item.setSendAll(),
199
+ child: Container(
200
+ decoration: BoxDecoration(
201
+ color: Theme.of(context)
202
+ .primaryTextTheme
203
+ .display1
204
+ .color,
205
+ borderRadius:
206
+ BorderRadius.all(
207
+ Radius.circular(6))),
208
+ child: Center(
209
+ child: Text(
210
+ S.of(context).all,
211
+ textAlign:
212
+ TextAlign.center,
213
+ style: TextStyle(
214
+ fontSize: 12,
215
+ fontWeight:
216
+ FontWeight.bold,
217
+ color:
218
+ Theme.of(context)
219
+ .primaryTextTheme
220
+ .display1
221
+ .decorationColor))),
222
+ ))))])
223
+ )),
224
+ Observer(
225
+ builder: (_) => Padding(
226
+ padding: EdgeInsets.only(top: 10),
227
+ child: Row(
228
+ mainAxisSize: MainAxisSize.max,
229
+ mainAxisAlignment:
230
+ MainAxisAlignment.spaceBetween,
231
+ children: <Widget>[
232
+ Expanded(
233
+ child: Text(
234
+ S.of(context).available_balance +
235
+ ':',
236
+ style: TextStyle(
237
+ fontSize: 12,
238
+ fontWeight: FontWeight.w600,
239
+ color: Theme.of(context)
240
+ .primaryTextTheme
241
+ .headline
242
+ .decorationColor),
243
+ )),
244
+ Text(
245
+ sendViewModel.balance,
246
+ style: TextStyle(
247
+ fontSize: 12,
248
+ fontWeight: FontWeight.w600,
249
+ color: Theme.of(context)
250
+ .primaryTextTheme
251
+ .headline
252
+ .decorationColor),
253
+ )
254
+ ],
255
+ ),
256
+ )),
257
+ Padding(
258
+ padding: const EdgeInsets.only(top: 20),
259
+ child: BaseTextFormField(
260
+ focusNode: fiatAmountFocus,
261
+ controller: fiatAmountController,
262
+ keyboardType:
263
+ TextInputType.numberWithOptions(
264
+ signed: false, decimal: true),
265
+ prefixIcon: Padding(
266
+ padding: EdgeInsets.only(top: 9),
267
+ child:
268
+ Text(sendViewModel.fiat.title + ':',
269
+ style: TextStyle(
270
+ fontSize: 16,
271
+ fontWeight: FontWeight.w600,
272
+ color: Colors.white,
273
+ )),
274
+ ),
275
+ hintText: '0.00',
276
+ borderColor: Theme.of(context)
277
+ .primaryTextTheme
278
+ .headline
279
+ .color,
280
+ textStyle: TextStyle(
281
+ fontSize: 14,
282
+ fontWeight: FontWeight.w500,
283
+ color: Colors.white),
284
+ placeholderTextStyle: TextStyle(
285
+ color: Theme.of(context)
286
+ .primaryTextTheme
287
+ .headline
288
+ .decorationColor,
289
+ fontWeight: FontWeight.w500,
290
+ fontSize: 14),
291
+ )),
292
+ Padding(
293
+ padding: EdgeInsets.only(top: 20),
294
+ child: BaseTextFormField(
295
+ controller: noteController,
296
+ keyboardType: TextInputType.multiline,
297
+ maxLines: null,
298
+ borderColor: Theme.of(context)
299
+ .primaryTextTheme
300
+ .headline
301
+ .color,
302
+ textStyle: TextStyle(
303
+ fontSize: 14,
304
+ fontWeight: FontWeight.w500,
305
+ color: Colors.white),
306
+ hintText: S.of(context).note_optional,
307
+ placeholderTextStyle: TextStyle(
308
+ fontSize: 14,
309
+ fontWeight: FontWeight.w500,
310
+ color: Theme.of(context)
311
+ .primaryTextTheme
312
+ .headline
313
+ .decorationColor),
314
+ ),
315
+ ),
316
+ Observer(
317
+ builder: (_) => GestureDetector(
318
+ onTap: () =>
319
+ _setTransactionPriority(context),
320
+ child: Container(
321
+ padding: EdgeInsets.only(top: 24),
322
+ child: Row(
323
+ mainAxisAlignment:
324
+ MainAxisAlignment.spaceBetween,
325
+ crossAxisAlignment: CrossAxisAlignment.start,
326
+ children: <Widget>[
327
+ Text(
328
+ S
329
+ .of(context)
330
+ .send_estimated_fee,
331
+ style: TextStyle(
332
+ fontSize: 12,
333
+ fontWeight:
334
+ FontWeight.w500,
335
+ //color: Theme.of(context).primaryTextTheme.display2.color,
336
+ color: Colors.white)),
337
+ Container(
338
+ child: Row(
339
+ crossAxisAlignment: CrossAxisAlignment.start,
340
+ children: <Widget>[
341
+ Column(
342
+ mainAxisAlignment: MainAxisAlignment.start,
343
+ crossAxisAlignment: CrossAxisAlignment.end,
344
+ children: [
345
+ Text(
346
+ item
347
+ .estimatedFee
348
+ .toString() +
349
+ ' ' +
350
+ sendViewModel
351
+ .currency.title,
352
+ style: TextStyle(
353
+ fontSize: 12,
354
+ fontWeight:
355
+ FontWeight.w600,
356
+ //color: Theme.of(context).primaryTextTheme.display2.color,
357
+ color:
358
+ Colors.white)),
359
+ Padding(
360
+ padding:
361
+ EdgeInsets.only(top: 5),
362
+ child: Text(
363
+ item
364
+ .estimatedFeeFiatAmount
365
+ + ' ' +
366
+ sendViewModel
367
+ .fiat.title,
368
+ style: TextStyle(
369
+ fontSize: 12,
370
+ fontWeight:
371
+ FontWeight.w600,
372
+ color: Theme
373
+ .of(context)
374
+ .primaryTextTheme
375
+ .headline
376
+ .decorationColor))
377
+ ),
378
+ ],
379
+ ),
380
+ Padding(
381
+ padding: EdgeInsets.only(
382
+ top: 2,
383
+ left: 5),
384
+ child: Icon(
385
+ Icons.arrow_forward_ios,
386
+ size: 12,
387
+ color: Colors.white,
388
+ ),
389
+ )
390
+ ],
391
+ ),
392
+ )
393
+ ],
394
+ ),
395
+ ),
396
+ ))
397
+ ],
398
+ )
399
+ ),
400
+ ),
401
+ ));
402
+ }
403
+
404
+ void _setEffects(BuildContext context) {
405
+ addressController.text = item.address;
406
+ cryptoAmountController.text = item.cryptoAmount;
407
+ fiatAmountController.text = item.fiatAmount;
408
+ noteController.text = item.note;
409
+
410
+ if (_effectsInstalled) {
411
+ return;
412
+ }
413
+
414
+ cryptoAmountController.addListener(() {
415
+ final amount = cryptoAmountController.text;
416
+
417
+ if (item.sendAll && amount != S.current.all) {
418
+ item.sendAll = false;
419
+ }
420
+
421
+ if (amount != item.cryptoAmount) {
422
+ item.setCryptoAmount(amount);
423
+ }
424
+ });
425
+
426
+ fiatAmountController.addListener(() {
427
+ final amount = fiatAmountController.text;
428
+
429
+ if (amount != item.fiatAmount) {
430
+ item.sendAll = false;
431
+ item.setFiatAmount(amount);
432
+ }
433
+ });
434
+
435
+ noteController.addListener(() {
436
+ final note = noteController.text ?? '';
437
+
438
+ if (note != item.note) {
439
+ item.note = note;
440
+ }
441
+ });
442
+
443
+ reaction((_) => item.sendAll, (bool all) {
444
+ if (all) {
445
+ cryptoAmountController.text = S.current.all;
446
+ fiatAmountController.text = null;
447
+ }
448
+ });
449
+
450
+ reaction((_) => item.fiatAmount, (String amount) {
451
+ if (amount != fiatAmountController.text) {
452
+ fiatAmountController.text = amount;
453
+ }
454
+ });
455
+
456
+ reaction((_) => item.cryptoAmount, (String amount) {
457
+ if (item.sendAll && amount != S.current.all) {
458
+ item.sendAll = false;
459
+ }
460
+
461
+ if (amount != cryptoAmountController.text) {
462
+ cryptoAmountController.text = amount;
463
+ }
464
+ });
465
+
466
+ reaction((_) => item.address, (String address) {
467
+ if (address != addressController.text) {
468
+ addressController.text = address;
469
+ }
470
+ });
471
+
472
+ addressController.addListener(() {
473
+ final address = addressController.text;
474
+
475
+ if (item.address != address) {
476
+ item.address = address;
477
+ }
478
+ });
479
+
480
+ reaction((_) => item.note, (String note) {
481
+ if (note != noteController.text) {
482
+ noteController.text = note;
483
+ }
484
+ });
485
+
486
+ addressFocusNode.addListener(() async {
487
+ if (!addressFocusNode.hasFocus && addressController.text.isNotEmpty) {
488
+ final record = await item.getOpenaliasRecord();
489
+
490
+ if (record != null) {
491
+ showAddressAlert(
492
+ context,
493
+ S.current.openalias_alert_title,
494
+ S.current.openalias_alert_content(record.name));
495
+ }
496
+ }
497
+ });
498
+
499
+ _effectsInstalled = true;
500
+ }
501
+
502
+ Future<void> _setTransactionPriority(BuildContext context) async {
503
+ final items = priorityForWalletType(sendViewModel.walletType);
504
+ final selectedItem = items.indexOf(sendViewModel.transactionPriority);
505
+
506
+ await showPopUp<void>(
507
+ builder: (_) => Picker(
508
+ items: items,
509
+ displayItem: sendViewModel.displayFeeRate,
510
+ selectedAtIndex: selectedItem,
511
+ title: S.of(context).please_select,
512
+ mainAxisAlignment: MainAxisAlignment.center,
513
+ onItemSelected: (TransactionPriority priority) =>
514
+ sendViewModel.setTransactionPriority(priority),
515
+ ),
516
+ context: context);
517
+ }
518
+
519
+ @override
520
+ bool get wantKeepAlive => true;
521
+}
\ No newline at end of file
lib/themes/bright_theme.dart
+2
@@ -105,10 +105,12 @@ class BrightTheme extends ThemeBase {
105
),
106
display2: TextStyle(
107
color: Colors.white.withOpacity(0.5), // estimated fee (send page)
108
+ backgroundColor: PaletteDark.darkCyanBlue.withOpacity(0.67), // dot color for indicator on send page
109
decorationColor: Palette.shadowWhite // template dotted border (send page)
110
),
111
display3: TextStyle(
112
color: Palette.darkBlueCraiola, // template new text (send page)
113
+ backgroundColor: PaletteDark.darkNightBlue, // active dot color for indicator on send page
114
decorationColor: Palette.shadowWhite // template background color (send page)
115
),
116
display4: TextStyle(
lib/themes/dark_theme.dart
+2
@@ -104,10 +104,12 @@ class DarkTheme extends ThemeBase {
104
),
105
display2: TextStyle(
106
color: Colors.white, // estimated fee (send page)
107
+ backgroundColor: PaletteDark.cyanBlue, // dot color for indicator on send page
108
decorationColor: PaletteDark.darkCyanBlue // template dotted border (send page)
109
),
110
display3: TextStyle(
111
color: PaletteDark.darkCyanBlue, // template new text (send page)
112
+ backgroundColor: Colors.white, // active dot color for indicator on send page
113
decorationColor: PaletteDark.darkVioletBlue // template background color (send page)
114
),
115
display4: TextStyle(
lib/themes/light_theme.dart
+2
@@ -105,10 +105,12 @@ class LightTheme extends ThemeBase {
105
),
106
display2: TextStyle(
107
color: Colors.white.withOpacity(0.5), // estimated fee (send page)
108
+ backgroundColor: PaletteDark.darkCyanBlue.withOpacity(0.67), // dot color for indicator on send page
109
decorationColor: Palette.moderateLavender // template dotted border (send page)
110
),
111
display3: TextStyle(
112
color: Palette.darkBlueCraiola, // template new text (send page)
113
+ backgroundColor: PaletteDark.darkNightBlue, // active dot color for indicator on send page
114
decorationColor: Palette.blueAlice // template background color (send page)
115
),
116
display4: TextStyle(
lib/view_model/exchange/exchange_trade_view_model.dart
+5
-2
@@ -79,8 +79,11 @@ abstract class ExchangeTradeViewModelBase with Store {
79
return;
80
}
81
82
- sendViewModel.address = trade.inputAddress;
83
- sendViewModel.setCryptoAmount(trade.amount);
82
+ sendViewModel.clearSendItemList();
83
+ final item = sendViewModel.sendItemList.first;
84
+
85
+ item.address = trade.inputAddress;
86
+ item.setCryptoAmount(trade.amount);
87
await sendViewModel.createTransaction();
88
}
89
lib/view_model/send/send_item.dart
new
+198
@@ -0,0 +1,198 @@
1
+import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
2
+import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
3
+import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
4
+import 'package:cake_wallet/entities/openalias_record.dart';
5
+import 'package:cake_wallet/monero/monero_amount_format.dart';
6
+import 'package:flutter/material.dart';
7
+import 'package:intl/intl.dart';
8
+import 'package:mobx/mobx.dart';
9
+import 'package:cake_wallet/core/wallet_base.dart';
10
+import 'package:cake_wallet/monero/monero_wallet.dart';
11
+import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
12
+import 'package:cake_wallet/entities/wallet_type.dart';
13
+import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
14
+import 'package:cake_wallet/store/settings_store.dart';
15
+import 'package:cake_wallet/generated/i18n.dart';
16
+
17
+part 'send_item.g.dart';
18
+
19
+const String cryptoNumberPattern = '0.0';
20
+
21
+class SendItem = SendItemBase with _$SendItem;
22
+
23
+abstract class SendItemBase with Store {
24
+ SendItemBase(this._wallet, this._settingsStore, this._fiatConversationStore)
25
+ :_cryptoNumberFormat = NumberFormat(cryptoNumberPattern) {
26
+ reset();
27
+ _setCryptoNumMaximumFractionDigits();
28
+ key = UniqueKey();
29
+ }
30
+
31
+ Key key;
32
+
33
+ @observable
34
+ String fiatAmount;
35
+
36
+ @observable
37
+ String cryptoAmount;
38
+
39
+ @observable
40
+ String address;
41
+
42
+ @observable
43
+ String note;
44
+
45
+ @observable
46
+ bool sendAll;
47
+
48
+ @computed
49
+ double get estimatedFee {
50
+ int amount;
51
+
52
+ try {
53
+ if (cryptoAmount?.isNotEmpty ?? false) {
54
+ final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
55
+ int _amount = 0;
56
+ switch (walletType) {
57
+ case WalletType.monero:
58
+ _amount = moneroParseAmount(amount: _cryptoAmount);
59
+ break;
60
+ case WalletType.bitcoin:
61
+ _amount = stringDoubleToBitcoinAmount(_cryptoAmount);
62
+ break;
63
+ case WalletType.litecoin:
64
+ _amount = stringDoubleToBitcoinAmount(_cryptoAmount);
65
+ break;
66
+ default:
67
+ break;
68
+ }
69
+
70
+ if (_amount > 0) {
71
+ amount = _amount;
72
+ }
73
+ }
74
+
75
+ final fee = _wallet.calculateEstimatedFee(
76
+ _settingsStore.priority[_wallet.type], amount);
77
+
78
+ if (_wallet is ElectrumWallet) {
79
+ return bitcoinAmountToDouble(amount: fee);
80
+ }
81
+
82
+ if (_wallet is MoneroWallet) {
83
+ return moneroAmountToDouble(amount: fee);
84
+ }
85
+ } catch (e) {
86
+ print(e.toString());
87
+ }
88
+
89
+ return 0;
90
+ }
91
+
92
+ @computed
93
+ String get estimatedFeeFiatAmount {
94
+ try {
95
+ final fiat = calculateFiatAmountRaw(
96
+ price: _fiatConversationStore.prices[_wallet.currency],
97
+ cryptoAmount: estimatedFee);
98
+ return fiat;
99
+ } catch (_) {
100
+ return '0.00';
101
+ }
102
+ }
103
+
104
+ WalletType get walletType => _wallet.type;
105
+ final WalletBase _wallet;
106
+ final SettingsStore _settingsStore;
107
+ final FiatConversionStore _fiatConversationStore;
108
+ final NumberFormat _cryptoNumberFormat;
109
+
110
+ @action
111
+ void setSendAll() => sendAll = true;
112
+
113
+ @action
114
+ void reset() {
115
+ sendAll = false;
116
+ cryptoAmount = '';
117
+ fiatAmount = '';
118
+ address = '';
119
+ note = '';
120
+ }
121
+
122
+ @action
123
+ void setCryptoAmount(String amount) {
124
+ if (amount.toUpperCase() != S.current.all) {
125
+ sendAll = false;
126
+ }
127
+
128
+ cryptoAmount = amount;
129
+ _updateFiatAmount();
130
+ }
131
+
132
+ @action
133
+ void setFiatAmount(String amount) {
134
+ fiatAmount = amount;
135
+ _updateCryptoAmount();
136
+ }
137
+
138
+ @action
139
+ void _updateFiatAmount() {
140
+ try {
141
+ final fiat = calculateFiatAmount(
142
+ price: _fiatConversationStore.prices[_wallet.currency],
143
+ cryptoAmount: cryptoAmount.replaceAll(',', '.'));
144
+ if (fiatAmount != fiat) {
145
+ fiatAmount = fiat;
146
+ }
147
+ } catch (_) {
148
+ fiatAmount = '';
149
+ }
150
+ }
151
+
152
+ @action
153
+ void _updateCryptoAmount() {
154
+ try {
155
+ final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
156
+ _fiatConversationStore.prices[_wallet.currency];
157
+ final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
158
+
159
+ if (cryptoAmount != cryptoAmountTmp) {
160
+ cryptoAmount = cryptoAmountTmp;
161
+ }
162
+ } catch (e) {
163
+ cryptoAmount = '';
164
+ }
165
+ }
166
+
167
+ void _setCryptoNumMaximumFractionDigits() {
168
+ var maximumFractionDigits = 0;
169
+
170
+ switch (_wallet.type) {
171
+ case WalletType.monero:
172
+ maximumFractionDigits = 12;
173
+ break;
174
+ case WalletType.bitcoin:
175
+ maximumFractionDigits = 8;
176
+ break;
177
+ case WalletType.litecoin:
178
+ maximumFractionDigits = 8;
179
+ break;
180
+ default:
181
+ break;
182
+ }
183
+
184
+ _cryptoNumberFormat.maximumFractionDigits = maximumFractionDigits;
185
+ }
186
+
187
+ Future<OpenaliasRecord> getOpenaliasRecord() async {
188
+ final formattedName = OpenaliasRecord.formatDomainName(address);
189
+ final record = await OpenaliasRecord.fetchAddressAndName(formattedName);
190
+
191
+ if (record == null || record.address.contains(formattedName)) {
192
+ return null;
193
+ }
194
+
195
+ address = record.address;
196
+ return record;
197
+ }
198
+}
\ No newline at end of file
lib/view_model/send/send_template_view_model.dart
new
+66
@@ -0,0 +1,66 @@
1
+import 'package:cake_wallet/view_model/send/send_item.dart';
2
+import 'package:mobx/mobx.dart';
3
+import 'package:cake_wallet/entities/template.dart';
4
+import 'package:cake_wallet/store/templates/send_template_store.dart';
5
+import 'package:cake_wallet/core/template_validator.dart';
6
+import 'package:cake_wallet/core/address_validator.dart';
7
+import 'package:cake_wallet/core/amount_validator.dart';
8
+import 'package:cake_wallet/core/validator.dart';
9
+import 'package:cake_wallet/core/wallet_base.dart';
10
+import 'package:cake_wallet/entities/crypto_currency.dart';
11
+import 'package:cake_wallet/entities/fiat_currency.dart';
12
+import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
13
+import 'package:cake_wallet/store/settings_store.dart';
14
+
15
+part 'send_template_view_model.g.dart';
16
+
17
+class SendTemplateViewModel = SendTemplateViewModelBase
18
+ with _$SendTemplateViewModel;
19
+
20
+abstract class SendTemplateViewModelBase with Store {
21
+ SendTemplateViewModelBase(this._wallet, this._settingsStore,
22
+ this._sendTemplateStore, this._fiatConversationStore) {
23
+
24
+ sendItem = SendItem(_wallet, _settingsStore, _fiatConversationStore);
25
+ }
26
+
27
+ SendItem sendItem;
28
+
29
+ Validator get amountValidator => AmountValidator(type: _wallet.type);
30
+
31
+ Validator get addressValidator => AddressValidator(type: _wallet.currency);
32
+
33
+ Validator get templateValidator => TemplateValidator();
34
+
35
+ CryptoCurrency get currency => _wallet.currency;
36
+
37
+ FiatCurrency get fiat => _settingsStore.fiatCurrency;
38
+
39
+ @computed
40
+ ObservableList<Template> get templates => _sendTemplateStore.templates;
41
+
42
+ final WalletBase _wallet;
43
+ final SettingsStore _settingsStore;
44
+ final SendTemplateStore _sendTemplateStore;
45
+ final FiatConversionStore _fiatConversationStore;
46
+
47
+ void updateTemplate() => _sendTemplateStore.update();
48
+
49
+ void addTemplate(
50
+ {String name,
51
+ String address,
52
+ String cryptoCurrency,
53
+ String amount}) {
54
+ _sendTemplateStore.addTemplate(
55
+ name: name,
56
+ address: address,
57
+ cryptoCurrency: cryptoCurrency,
58
+ amount: amount);
59
+ updateTemplate();
60
+ }
61
+
62
+ void removeTemplate({Template template}) {
63
+ _sendTemplateStore.remove(template: template);
64
+ updateTemplate();
65
+ }
66
+}
lib/view_model/send/send_view_model.dart
+30
-180
@@ -6,6 +6,8 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
6
import 'package:cake_wallet/entities/transaction_description.dart';
7
import 'package:cake_wallet/entities/transaction_priority.dart';
8
import 'package:cake_wallet/monero/monero_amount_format.dart';
9
+import 'package:cake_wallet/view_model/send/send_item.dart';
10
+import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
11
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
12
import 'package:hive/hive.dart';
13
import 'package:intl/intl.dart';
@@ -37,17 +39,13 @@ import 'package:cake_wallet/generated/i18n.dart';
39
40
part 'send_view_model.g.dart';
41
40
-const String cryptoNumberPattern = '0.0';
41
-
42
class SendViewModel = SendViewModelBase with _$SendViewModel;
43
44
abstract class SendViewModelBase with Store {
45
- SendViewModelBase(this._wallet, this._settingsStore, this._sendTemplateStore,
46
- this._fiatConversationStore, this.transactionDescriptionBox)
47
- : state = InitialExecutionState(),
48
- _cryptoNumberFormat = NumberFormat(cryptoNumberPattern),
49
- note = '',
50
- sendAll = false {
45
+ SendViewModelBase(this._wallet, this._settingsStore,
46
+ this.sendTemplateViewModel, this._fiatConversationStore,
47
+ this.transactionDescriptionBox)
48
+ : state = InitialExecutionState() {
49
final priority = _settingsStore.priority[_wallet.type];
50
final priorities = priorityForWalletType(_wallet.type);
51
@@ -55,82 +53,33 @@ abstract class SendViewModelBase with Store {
53
_settingsStore.priority[_wallet.type] = priorities.first;
54
}
55
58
- _setCryptoNumMaximumFractionDigits();
56
+ sendItemList = ObservableList<SendItem>()
57
+ ..add(SendItem(_wallet, _settingsStore, _fiatConversationStore));
58
}
59
60
@observable
61
ExecutionState state;
62
64
- @observable
65
- String fiatAmount;
66
-
67
- @observable
68
- String cryptoAmount;
69
-
70
- @observable
71
- String address;
72
-
73
- @observable
74
- String note;
75
-
76
- @observable
77
- bool sendAll;
63
+ ObservableList<SendItem> sendItemList;
64
79
- @computed
80
- double get estimatedFee {
81
- int amount;
82
-
83
- try {
84
- if (cryptoAmount?.isNotEmpty ?? false) {
85
- final _cryptoAmount = cryptoAmount.replaceAll(',', '.');
86
- int _amount = 0;
87
- switch (walletType) {
88
- case WalletType.monero:
89
- _amount = moneroParseAmount(amount: _cryptoAmount);
90
- break;
91
- case WalletType.bitcoin:
92
- _amount = stringDoubleToBitcoinAmount(_cryptoAmount);
93
- break;
94
- case WalletType.litecoin:
95
- _amount = stringDoubleToBitcoinAmount(_cryptoAmount);
96
- break;
97
- default:
98
- break;
99
- }
100
-
101
- if (_amount > 0) {
102
- amount = _amount;
103
- }
104
- }
105
-
106
- final fee = _wallet.calculateEstimatedFee(
107
- _settingsStore.priority[_wallet.type], amount);
108
-
109
- if (_wallet is ElectrumWallet) {
110
- return bitcoinAmountToDouble(amount: fee);
111
- }
65
+ @action
66
+ void addSendItem() {
67
+ sendItemList.add(SendItem(_wallet, _settingsStore, _fiatConversationStore));
68
+ }
69
113
- if (_wallet is MoneroWallet) {
114
- return moneroAmountToDouble(amount: fee);
115
- }
116
- } catch (e) {
117
- print(e.toString());
118
- }
70
+ @action
71
+ void removeSendItem(SendItem item) {
72
+ sendItemList.remove(item);
73
+ }
74
120
- return 0;
75
+ @action
76
+ void clearSendItemList() {
77
+ sendItemList.clear();
78
+ addSendItem();
79
}
80
81
@computed
124
- String get estimatedFeeFiatAmount {
125
- try {
126
- final fiat = calculateFiatAmountRaw(
127
- price: _fiatConversationStore.prices[_wallet.currency],
128
- cryptoAmount: estimatedFee);
129
- return fiat;
130
- } catch (_) {
131
- return '0.00';
132
- }
133
- }
82
+ bool get isRemoveButtonShow => sendItemList.length > 1;
83
84
@computed
85
String get pendingTransactionFiatAmount {
@@ -177,8 +126,6 @@ abstract class SendViewModelBase with Store {
126
127
Validator get addressValidator => AddressValidator(type: _wallet.currency);
128
180
- Validator get templateValidator => TemplateValidator();
181
-
129
@observable
130
PendingTransaction pendingTransaction;
131
@@ -189,28 +136,15 @@ abstract class SendViewModelBase with Store {
136
bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus;
137
138
@computed
192
- ObservableList<Template> get templates => _sendTemplateStore.templates;
139
+ ObservableList<Template> get templates => sendTemplateViewModel.templates;
140
141
WalletType get walletType => _wallet.type;
142
final WalletBase _wallet;
143
final SettingsStore _settingsStore;
197
- final SendTemplateStore _sendTemplateStore;
144
+ final SendTemplateViewModel sendTemplateViewModel;
145
final FiatConversionStore _fiatConversationStore;
199
- final NumberFormat _cryptoNumberFormat;
146
final Box<TransactionDescription> transactionDescriptionBox;
147
202
- @action
203
- void setSendAll() => sendAll = true;
204
-
205
- @action
206
- void reset() {
207
- sendAll = false;
208
- cryptoAmount = '';
209
- fiatAmount = '';
210
- address = '';
211
- note = '';
212
- }
213
-
148
@action
149
Future<void> createTransaction() async {
150
try {
@@ -224,6 +158,8 @@ abstract class SendViewModelBase with Store {
158
159
@action
160
Future<void> commitTransaction() async {
161
+ final address = ''; // FIXME: get it from item
162
+ final note = ''; // FIXME: get it from item
163
try {
164
state = TransactionCommitting();
165
await pendingTransaction.commit();
@@ -244,64 +180,14 @@ abstract class SendViewModelBase with Store {
180
}
181
}
182
247
- @action
248
- void setCryptoAmount(String amount) {
249
- if (amount.toUpperCase() != S.current.all) {
250
- sendAll = false;
251
- }
252
-
253
- cryptoAmount = amount;
254
- _updateFiatAmount();
255
- }
256
-
257
- @action
258
- void setFiatAmount(String amount) {
259
- fiatAmount = amount;
260
- _updateCryptoAmount();
261
- }
262
-
183
@action
184
void setTransactionPriority(TransactionPriority priority) =>
185
_settingsStore.priority[_wallet.type] = priority;
186
267
- Future<OpenaliasRecord> decodeOpenaliasRecord(String name) async {
268
- final record = await OpenaliasRecord.fetchAddressAndName(
269
- OpenaliasRecord.formatDomainName(name));
270
-
271
- return record.name != name ? record : null;
272
- }
273
-
274
- @action
275
- void _updateFiatAmount() {
276
- try {
277
- final fiat = calculateFiatAmount(
278
- price: _fiatConversationStore.prices[_wallet.currency],
279
- cryptoAmount: cryptoAmount.replaceAll(',', '.'));
280
- if (fiatAmount != fiat) {
281
- fiatAmount = fiat;
282
- }
283
- } catch (_) {
284
- fiatAmount = '';
285
- }
286
- }
287
-
288
- @action
289
- void _updateCryptoAmount() {
290
- try {
291
- final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
292
- _fiatConversationStore.prices[_wallet.currency];
293
- final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
294
-
295
- if (cryptoAmount != cryptoAmountTmp) {
296
- cryptoAmount = cryptoAmountTmp;
297
- }
298
- } catch (e) {
299
- cryptoAmount = '';
300
- }
301
- }
302
-
187
Object _credentials() {
304
- final _amount = cryptoAmount.replaceAll(',', '.');
188
+ // FIXME: get it from item
189
+ return null;
190
+ /*final _amount = cryptoAmount.replaceAll(',', '.');
191
192
switch (_wallet.type) {
193
case WalletType.bitcoin:
@@ -327,45 +213,9 @@ abstract class SendViewModelBase with Store {
213
amount: amount);
214
default:
215
return null;
330
- }
331
- }
332
-
333
- void _setCryptoNumMaximumFractionDigits() {
334
- var maximumFractionDigits = 0;
335
-
336
- switch (_wallet.type) {
337
- case WalletType.monero:
338
- maximumFractionDigits = 12;
339
- break;
340
- case WalletType.bitcoin:
341
- maximumFractionDigits = 8;
342
- break;
343
- case WalletType.litecoin:
344
- maximumFractionDigits = 8;
345
- break;
346
- default:
347
- break;
348
- }
349
-
350
- _cryptoNumberFormat.maximumFractionDigits = maximumFractionDigits;
216
+ }*/
217
}
218
353
- void updateTemplate() => _sendTemplateStore.update();
354
-
355
- void addTemplate(
356
- {String name,
357
- String address,
358
- String cryptoCurrency,
359
- String amount}) =>
360
- _sendTemplateStore.addTemplate(
361
- name: name,
362
- address: address,
363
- cryptoCurrency: cryptoCurrency,
364
- amount: amount);
365
-
366
- void removeTemplate({Template template}) =>
367
- _sendTemplateStore.remove(template: template);
368
-
219
String displayFeeRate(dynamic priority) {
220
final _priority = priority as TransactionPriority;
221
final wallet = _wallet;