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