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;