CAKE-198 | applied ability to make a note with some details about transaction; added transactionNote property and note getter to transaction_description.dart; added multiline textfield for notes (send_page.dart); added transaction_details_list_item.dart, textfield_list_item.dart, textfield_list_row.dart, transaction_details_view_model.dart to the app

OleksandrSobol committed Dec 22, 2020 at 20:42 UTC 023336d46026d88db5add907797eba02038eef66
11 files changed +312 -95
lib/di.dart
+10 -3
@@ -55,6 +55,7 @@ import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
55 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
56 import 'package:cake_wallet/view_model/rescan_view_model.dart';
57 import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
58 +import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
59 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
60 import 'package:cake_wallet/view_model/auth_view_model.dart';
61 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@@ -415,11 +416,17 @@ Future setup(
416 getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
417 WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
418
419 + getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>
420 + ((TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
421 + transactionInfo: transactionInfo,
422 + transactionDescriptionBox: transactionDescriptionBox,
423 + settingsStore: getIt.get<SettingsStore>()
424 + ));
425 +
426 getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
427 (TransactionInfo transactionInfo, _) => TransactionDetailsPage(
420 - transactionInfo,
421 - getIt.get<SettingsStore>().shouldSaveRecipientAddress,
422 - transactionDescriptionBox));
428 + transactionDetailsViewModel: getIt
429 + .get<TransactionDetailsViewModel>(param1: transactionInfo)));
430
431 getIt.registerFactoryParam<NewWalletTypePage,
432 void Function(BuildContext, WalletType), bool>(
lib/entities/transaction_description.dart
+6 -1
@@ -4,7 +4,7 @@ part 'transaction_description.g.dart';
4
5 @HiveType(typeId: 2)
6 class TransactionDescription extends HiveObject {
7 - TransactionDescription({this.id, this.recipientAddress});
7 + TransactionDescription({this.id, this.recipientAddress, this.transactionNote});
8
9 static const boxName = 'TransactionDescriptions';
10 static const boxKey = 'transactionDescriptionsBoxKey';
@@ -14,4 +14,9 @@ class TransactionDescription extends HiveObject {
14
15 @HiveField(1)
16 String recipientAddress;
17 +
18 + @HiveField(2)
19 + String transactionNote;
20 +
21 + String get note => transactionNote ?? '';
22 }
lib/src/screens/send/send_page.dart
+40
@@ -31,6 +31,7 @@ class SendPage extends BasePage {
31 : _addressController = TextEditingController(),
32 _cryptoAmountController = TextEditingController(),
33 _fiatAmountController = TextEditingController(),
34 + _noteController = TextEditingController(),
35 _formKey = GlobalKey<FormState>(),
36 _cryptoAmountFocus = FocusNode(),
37 _fiatAmountFocus = FocusNode(),
@@ -46,6 +47,7 @@ class SendPage extends BasePage {
47 final TextEditingController _addressController;
48 final TextEditingController _cryptoAmountController;
49 final TextEditingController _fiatAmountController;
50 + final TextEditingController _noteController;
51 final GlobalKey<FormState> _formKey;
52 final FocusNode _cryptoAmountFocus;
53 final FocusNode _fiatAmountFocus;
@@ -304,6 +306,30 @@ class SendPage extends BasePage {
306 fontWeight: FontWeight.w500,
307 fontSize: 14),
308 )),
309 + Padding(
310 + padding: EdgeInsets.only(top: 20),
311 + child: BaseTextFormField(
312 + controller: _noteController,
313 + keyboardType: TextInputType.multiline,
314 + maxLines: null,
315 + borderColor: Theme.of(context)
316 + .primaryTextTheme
317 + .headline
318 + .color,
319 + textStyle: TextStyle(
320 + fontSize: 14,
321 + fontWeight: FontWeight.w500,
322 + color: Colors.white),
323 + hintText: 'Note (optional)',
324 + placeholderTextStyle: TextStyle(
325 + fontSize: 14,
326 + fontWeight: FontWeight.w500,
327 + color: Theme.of(context)
328 + .primaryTextTheme
329 + .headline
330 + .decorationColor),
331 + ),
332 + ),
333 Observer(
334 builder: (_) => GestureDetector(
335 onTap: () =>
@@ -534,6 +560,14 @@ class SendPage extends BasePage {
560 }
561 });
562
563 + _noteController.addListener(() {
564 + final note = _noteController.text ?? '';
565 +
566 + if (note != sendViewModel.note) {
567 + sendViewModel.note = note;
568 + }
569 + });
570 +
571 reaction((_) => sendViewModel.sendAll, (bool all) {
572 if (all) {
573 _cryptoAmountController.text = S.current.all;
@@ -571,6 +605,12 @@ class SendPage extends BasePage {
605 }
606 });
607
608 + reaction((_) => sendViewModel.note, (String note) {
609 + if (note != _noteController.text) {
610 + _noteController.text = note;
611 + }
612 + });
613 +
614 reaction((_) => sendViewModel.state, (ExecutionState state) {
615 if (state is FailureState) {
616 WidgetsBinding.instance.addPostFrameCallback((_) {
lib/src/screens/transaction_details/standart_list_item.dart
+4 -4
@@ -1,6 +1,6 @@
1 -class StandartListItem {
2 - StandartListItem({this.title, this.value});
1 +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2
4 - final String title;
5 - final String value;
3 +class StandartListItem extends TransactionDetailsListItem {
4 + StandartListItem({String title, String value})
5 + : super(title: title, value: value);
6 }
lib/src/screens/transaction_details/textfield_list_item.dart new
+8
@@ -0,0 +1,8 @@
1 +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
2 +
3 +class TextFieldListItem extends TransactionDetailsListItem {
4 + TextFieldListItem({String title, String value, this.onSubmitted})
5 + : super(title: title, value: value);
6 +
7 + final Function(String value) onSubmitted;
8 +}
\ No newline at end of file
lib/src/screens/transaction_details/transaction_details_list_item.dart new
+6
@@ -0,0 +1,6 @@
1 +abstract class TransactionDetailsListItem {
2 + TransactionDetailsListItem({this.title, this.value});
3 +
4 + final String title;
5 + final String value;
6 +}
\ No newline at end of file
lib/src/screens/transaction_details/transaction_details_page.dart
+34 -85
@@ -1,87 +1,21 @@
1 -import 'package:cake_wallet/entities/transaction_description.dart';
1 +import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
2 +import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
3 import 'package:cake_wallet/utils/show_bar.dart';
4 +import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
5 import 'package:flutter/material.dart';
6 import 'package:flutter/services.dart';
7 import 'package:cake_wallet/generated/i18n.dart';
6 -import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
7 -import 'package:cake_wallet/monero/monero_transaction_info.dart';
8 -import 'package:cake_wallet/entities/transaction_info.dart';
8 import 'package:cake_wallet/src/widgets/standart_list_row.dart';
9 import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
10 import 'package:cake_wallet/src/screens/base_page.dart';
12 -import 'package:cake_wallet/utils/date_formatter.dart';
13 -import 'package:hive/hive.dart';
11
12 class TransactionDetailsPage extends BasePage {
16 - TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress,
17 - Box<TransactionDescription> transactionDescriptionBox)
18 - : _items = [] {
19 - final dateFormat = DateFormatter.withCurrentLocal();
20 - final tx = transactionInfo;
21 -
22 - if (tx is MoneroTransactionInfo) {
23 - final items = [
24 - StandartListItem(
25 - title: S.current.transaction_details_transaction_id, value: tx.id),
26 - StandartListItem(
27 - title: S.current.transaction_details_date,
28 - value: dateFormat.format(tx.date)),
29 - StandartListItem(
30 - title: S.current.transaction_details_height, value: '${tx.height}'),
31 - StandartListItem(
32 - title: S.current.transaction_details_amount,
33 - value: tx.amountFormatted()),
34 - StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
35 - ];
36 -
37 - if (tx.key?.isNotEmpty ?? null) {
38 - // FIXME: add translation
39 - items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
40 - }
41 -
42 - _items.addAll(items);
43 - }
44 -
45 - if (tx is BitcoinTransactionInfo) {
46 - final items = [
47 - StandartListItem(
48 - title: S.current.transaction_details_transaction_id, value: tx.id),
49 - StandartListItem(
50 - title: S.current.transaction_details_date,
51 - value: dateFormat.format(tx.date)),
52 - StandartListItem(
53 - title: 'Confirmations', value: tx.confirmations?.toString()),
54 - StandartListItem(
55 - title: S.current.transaction_details_height, value: '${tx.height}'),
56 - StandartListItem(
57 - title: S.current.transaction_details_amount,
58 - value: tx.amountFormatted()),
59 - if (tx.feeFormatted()?.isNotEmpty)
60 - StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
61 - ];
62 -
63 - _items.addAll(items);
64 - }
65 -
66 - if (showRecipientAddress) {
67 - final recipientAddress = transactionDescriptionBox.values
68 - .firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)
69 - ?.recipientAddress;
70 -
71 - if (recipientAddress?.isNotEmpty ?? false) {
72 - _items.add(StandartListItem(
73 - title: S.current.transaction_details_recipient_address,
74 - value: recipientAddress));
75 - }
76 - }
77 - }
13 + TransactionDetailsPage({this.transactionDetailsViewModel});
14
15 @override
16 String get title => S.current.transaction_details_title;
17
82 - final TransactionInfo transactionInfo;
83 -
84 - final List<StandartListItem> _items;
18 + final TransactionDetailsViewModel transactionDetailsViewModel;
19
20 @override
21 Widget body(BuildContext context) {
@@ -97,22 +31,37 @@ class TransactionDetailsPage extends BasePage {
31 Theme.of(context).primaryTextTheme.title.backgroundColor,
32 ),
33 ),
100 - itemCount: _items.length,
34 + itemCount: transactionDetailsViewModel.items.length,
35 itemBuilder: (context, index) {
102 - final item = _items[index];
103 - final isDrawBottom = index == _items.length - 1 ? true : false;
36 + final item = transactionDetailsViewModel.items[index];
37 + final isDrawBottom =
38 + index == transactionDetailsViewModel.items.length - 1
39 + ? true : false;
40 +
41 + if (item is StandartListItem) {
42 + return GestureDetector(
43 + onTap: () {
44 + Clipboard.setData(ClipboardData(text: item.value));
45 + showBar<void>(context,
46 + S.of(context).transaction_details_copied(item.title));
47 + },
48 + child: StandartListRow(
49 + title: '${item.title}:',
50 + value: item.value,
51 + isDrawBottom: isDrawBottom),
52 + );
53 + }
54 +
55 + if (item is TextFieldListItem) {
56 + return TextFieldListRow(
57 + title: item.title,
58 + value: item.value,
59 + onSubmitted: item.onSubmitted,
60 + isDrawBottom: isDrawBottom,
61 + );
62 + }
63
105 - return GestureDetector(
106 - onTap: () {
107 - Clipboard.setData(ClipboardData(text: item.value));
108 - showBar<void>(context,
109 - S.of(context).transaction_details_copied(item.title));
110 - },
111 - child: StandartListRow(
112 - title: '${item.title}:',
113 - value: item.value,
114 - isDrawBottom: isDrawBottom),
115 - );
64 + return null;
65 }),
66 );
67 }
lib/src/screens/transaction_details/widgets/textfield_list_row.dart new
+91
@@ -0,0 +1,91 @@
1 +import 'package:flutter/cupertino.dart';
2 +import 'package:flutter/material.dart';
3 +
4 +class TextFieldListRow extends StatelessWidget {
5 + TextFieldListRow(
6 + {this.title,
7 + this.value,
8 + this.titleFontSize = 14,
9 + this.valueFontSize = 16,
10 + this.onSubmitted,
11 + this.isDrawBottom = false}) {
12 +
13 + _textController = TextEditingController();
14 + _textController.text = value;
15 + }
16 +
17 + final String title;
18 + final String value;
19 + final double titleFontSize;
20 + final double valueFontSize;
21 + final Function(String value) onSubmitted;
22 + final bool isDrawBottom;
23 +
24 + TextEditingController _textController;
25 +
26 + @override
27 + Widget build(BuildContext context) {
28 + return Column(
29 + children: <Widget>[
30 + Container(
31 + width: double.infinity,
32 + color: Theme.of(context).backgroundColor,
33 + child: Padding(
34 + padding:
35 + const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
36 + child: Column(
37 + crossAxisAlignment: CrossAxisAlignment.start,
38 + children: <Widget>[
39 + Text(title,
40 + style: TextStyle(
41 + fontSize: titleFontSize,
42 + fontWeight: FontWeight.w500,
43 + color:
44 + Theme.of(context).primaryTextTheme.overline.color),
45 + textAlign: TextAlign.left),
46 + TextField(
47 + controller: _textController,
48 + keyboardType: TextInputType.multiline,
49 + textInputAction: TextInputAction.done,
50 + maxLines: null,
51 + textAlign: TextAlign.start,
52 + style: TextStyle(
53 + fontSize: valueFontSize,
54 + fontWeight: FontWeight.w500,
55 + color: Theme.of(context)
56 + .primaryTextTheme
57 + .title
58 + .color),
59 + decoration: InputDecoration(
60 + isDense: true,
61 + contentPadding: EdgeInsets.only(top: 12, bottom: 0),
62 + hintText: 'Note',
63 + hintStyle: TextStyle(
64 + fontSize: valueFontSize,
65 + fontWeight: FontWeight.w500,
66 + color: Theme.of(context)
67 + .primaryTextTheme
68 + .title
69 + .color),
70 + border: InputBorder.none
71 + ),
72 + onSubmitted: (value) => onSubmitted.call(value),
73 + )
74 + ]),
75 + ),
76 + ),
77 + isDrawBottom
78 + ? Container(
79 + height: 1,
80 + padding: EdgeInsets.only(left: 24),
81 + color: Theme.of(context).backgroundColor,
82 + child: Container(
83 + height: 1,
84 + color: Theme.of(context).primaryTextTheme.title.backgroundColor,
85 + ),
86 + )
87 + : Offstage(),
88 + ],
89 + );
90 + }
91 +}
lib/src/widgets/base_text_form_field.dart
+1 -1
@@ -51,7 +51,7 @@ class BaseTextFormField extends StatelessWidget {
51 final FocusNode focusNode;
52 final bool readOnly;
53 final bool enableInteractiveSelection;
54 - String initialValue;
54 + final String initialValue;
55
56 @override
57 Widget build(BuildContext context) {
lib/view_model/send/send_view_model.dart
+7 -1
@@ -37,6 +37,7 @@ abstract class SendViewModelBase with Store {
37 this._fiatConversationStore, this.transactionDescriptionBox)
38 : state = InitialExecutionState(),
39 _cryptoNumberFormat = NumberFormat(),
40 + note = '',
41 sendAll = false {
42 _setCryptoNumMaximumFractionDigits();
43 }
@@ -53,6 +54,9 @@ abstract class SendViewModelBase with Store {
54 @observable
55 String address;
56
57 + @observable
58 + String note;
59 +
60 @observable
61 bool sendAll;
62
@@ -105,6 +109,7 @@ abstract class SendViewModelBase with Store {
109 cryptoAmount = '';
110 fiatAmount = '';
111 address = '';
112 + note = '';
113 }
114
115 @action
@@ -127,7 +132,8 @@ abstract class SendViewModelBase with Store {
132 if (_settingsStore.shouldSaveRecipientAddress &&
133 (pendingTransaction.id?.isNotEmpty ?? false)) {
134 await transactionDescriptionBox.add(TransactionDescription(
130 - id: pendingTransaction.id, recipientAddress: address));
135 + id: pendingTransaction.id, recipientAddress: address,
136 + transactionNote: note));
137 }
138
139 state = TransactionCommitted();
lib/view_model/transaction_details_view_model.dart new
+105
@@ -0,0 +1,105 @@
1 +import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
2 +import 'package:cake_wallet/entities/transaction_info.dart';
3 +import 'package:cake_wallet/monero/monero_transaction_info.dart';
4 +import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
5 +import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
6 +import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
7 +import 'package:cake_wallet/utils/date_formatter.dart';
8 +import 'package:cake_wallet/entities/transaction_description.dart';
9 +import 'package:hive/hive.dart';
10 +import 'package:mobx/mobx.dart';
11 +import 'package:cake_wallet/store/settings_store.dart';
12 +import 'package:cake_wallet/generated/i18n.dart';
13 +
14 +part 'transaction_details_view_model.g.dart';
15 +
16 +class TransactionDetailsViewModel = TransactionDetailsViewModelBase with _$TransactionDetailsViewModel;
17 +
18 +abstract class TransactionDetailsViewModelBase with Store {
19 + TransactionDetailsViewModelBase({
20 + this.transactionInfo,
21 + this.transactionDescriptionBox,
22 + this.settingsStore}) : items = [] {
23 +
24 + showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
25 +
26 + final dateFormat = DateFormatter.withCurrentLocal();
27 + final tx = transactionInfo;
28 +
29 + if (tx is MoneroTransactionInfo) {
30 + final _items = [
31 + StandartListItem(
32 + title: S.current.transaction_details_transaction_id, value: tx.id),
33 + StandartListItem(
34 + title: S.current.transaction_details_date,
35 + value: dateFormat.format(tx.date)),
36 + StandartListItem(
37 + title: S.current.transaction_details_height, value: '${tx.height}'),
38 + StandartListItem(
39 + title: S.current.transaction_details_amount,
40 + value: tx.amountFormatted()),
41 + StandartListItem(title: S.current.send_fee, value: tx.feeFormatted()),
42 + ];
43 +
44 + if (tx.key?.isNotEmpty ?? null) {
45 + // FIXME: add translation
46 + _items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
47 + }
48 +
49 + items.addAll(_items);
50 + }
51 +
52 + if (tx is BitcoinTransactionInfo) {
53 + final _items = [
54 + StandartListItem(
55 + title: S.current.transaction_details_transaction_id, value: tx.id),
56 + StandartListItem(
57 + title: S.current.transaction_details_date,
58 + value: dateFormat.format(tx.date)),
59 + // FIXME: add translation
60 + StandartListItem(
61 + title: 'Confirmations', value: tx.confirmations?.toString()),
62 + StandartListItem(
63 + title: S.current.transaction_details_height, value: '${tx.height}'),
64 + StandartListItem(
65 + title: S.current.transaction_details_amount,
66 + value: tx.amountFormatted()),
67 + if (tx.feeFormatted()?.isNotEmpty)
68 + StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
69 + ];
70 +
71 + items.addAll(_items);
72 + }
73 +
74 + if (showRecipientAddress) {
75 + final recipientAddress = transactionDescriptionBox.values
76 + .firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)
77 + ?.recipientAddress;
78 +
79 + if (recipientAddress?.isNotEmpty ?? false) {
80 + items.add(StandartListItem(
81 + title: S.current.transaction_details_recipient_address,
82 + value: recipientAddress));
83 + }
84 + }
85 +
86 + final description = transactionDescriptionBox.values.firstWhere(
87 + (val) => val.id == transactionInfo.id, orElse: () => null);
88 +
89 + if (description != null) {
90 + // FIXME: add translation
91 + items.add(TextFieldListItem(title: 'Note (tap to change)',
92 + value: description.note, onSubmitted: (value) {
93 + description.transactionNote = value;
94 + description.save();
95 + }));
96 + }
97 + }
98 +
99 + final TransactionInfo transactionInfo;
100 + final Box<TransactionDescription> transactionDescriptionBox;
101 + final SettingsStore settingsStore;
102 +
103 + final List<TransactionDetailsListItem> items;
104 + bool showRecipientAddress;
105 +}
\ No newline at end of file