Added auto saving of wallet address to wallet info. Added users wallet addresses into address book.
M committed
Jan 8, 2021 at 20:10 UTC
bef18de7a6d50c08f1938888cd393119b0d3aa75
16 files changed
+238
-125
lib/bitcoin/bitcoin_wallet_service.dart
-1
@@ -89,7 +89,6 @@ class BitcoinWalletService extends WalletService<
89
walletInfo: credentials.walletInfo);
90
await wallet.save();
91
await wallet.init();
92
- await wallet.generateNewAddresses(32);
92
93
return wallet;
94
}
lib/di.dart
+10
-9
@@ -330,7 +330,8 @@ Future setup(
330
(ContactRecord contact, _) =>
331
ContactViewModel(contactSource, contact: contact));
332
333
- getIt.registerFactory(() => ContactListViewModel(contactSource));
333
+ getIt.registerFactory(
334
+ () => ContactListViewModel(contactSource, walletInfoSource));
335
336
getIt.registerFactoryParam<ContactListPage, bool, void>(
337
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@@ -419,17 +420,17 @@ Future setup(
420
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
421
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
422
422
- getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>
423
- ((TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
424
- transactionInfo: transactionInfo,
425
- transactionDescriptionBox: transactionDescriptionBox,
426
- settingsStore: getIt.get<SettingsStore>()
427
- ));
423
+ getIt
424
+ .registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
425
+ (TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
426
+ transactionInfo: transactionInfo,
427
+ transactionDescriptionBox: transactionDescriptionBox,
428
+ settingsStore: getIt.get<SettingsStore>()));
429
430
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
431
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
431
- transactionDetailsViewModel: getIt
432
- .get<TransactionDetailsViewModel>(param1: transactionInfo)));
432
+ transactionDetailsViewModel:
433
+ getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
434
435
getIt.registerFactoryParam<NewWalletTypePage,
436
void Function(BuildContext, WalletType), bool>(
lib/entities/contact_base.dart
new
+9
@@ -0,0 +1,9 @@
1
+import 'package:cake_wallet/entities/crypto_currency.dart';
2
+
3
+abstract class ContactBase {
4
+ String name;
5
+
6
+ String address;
7
+
8
+ CryptoCurrency type;
9
+}
\ No newline at end of file
lib/entities/contact_record.dart
+7
-1
@@ -3,21 +3,27 @@ import 'package:mobx/mobx.dart';
3
import 'package:cake_wallet/entities/contact.dart';
4
import 'package:cake_wallet/entities/crypto_currency.dart';
5
import 'package:cake_wallet/entities/record.dart';
6
+import 'package:cake_wallet/entities/contact_base.dart';
7
8
part 'contact_record.g.dart';
9
10
class ContactRecord = ContactRecordBase with _$ContactRecord;
11
11
-abstract class ContactRecordBase extends Record<Contact> with Store {
12
+abstract class ContactRecordBase extends Record<Contact>
13
+ with Store
14
+ implements ContactBase {
15
ContactRecordBase(Box<Contact> source, Contact original)
16
: super(source, original);
17
18
+ @override
19
@observable
20
String name;
21
22
+ @override
23
@observable
24
String address;
25
26
+ @override
27
@observable
28
CryptoCurrency type;
29
lib/entities/default_settings_migration.dart
+32
-1
@@ -1,4 +1,8 @@
1
-import 'dart:io' show Platform;
1
+import 'dart:io' show File, Platform;
2
+import 'package:cake_wallet/core/key_service.dart';
3
+import 'package:cake_wallet/di.dart';
4
+import 'package:cake_wallet/entities/pathForWallet.dart';
5
+import 'package:cake_wallet/monero/monero_wallet_service.dart';
6
import 'package:flutter/foundation.dart';
7
import 'package:hive/hive.dart';
8
import 'package:shared_preferences/shared_preferences.dart';
@@ -73,6 +77,9 @@ Future defaultSettingsMigration(
77
sharedPreferences: sharedPreferences, nodes: nodes);
78
break;
79
80
+ case 5:
81
+ await addAddressesForMoneroWallets(walletInfoSource);
82
+ break;
83
default:
84
break;
85
}
@@ -189,3 +196,27 @@ Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
196
final serverList = await loadElectrumServerList();
197
await nodes.addAll(serverList);
198
}
199
+
200
+Future<void> addAddressesForMoneroWallets(
201
+ Box<WalletInfo> walletInfoSource) async {
202
+ final moneroWalletsInfo =
203
+ walletInfoSource.values.where((info) => info.type == WalletType.monero);
204
+ moneroWalletsInfo.forEach((info) async {
205
+ try {
206
+ final walletPath =
207
+ await pathForWallet(name: info.name, type: WalletType.monero);
208
+ final addressFilePath = '$walletPath.address.txt';
209
+ final addressFile = File(addressFilePath);
210
+
211
+ if (!addressFile.existsSync()) {
212
+ return;
213
+ }
214
+
215
+ final addressText = await addressFile.readAsString();
216
+ info.address = addressText;
217
+ await info.save();
218
+ } catch (e) {
219
+ print(e.toString());
220
+ }
221
+ });
222
+}
lib/entities/wallet_contact.dart
new
+15
@@ -0,0 +1,15 @@
1
+import 'package:cake_wallet/entities/contact_base.dart';
2
+import 'package:cake_wallet/entities/crypto_currency.dart';
3
+
4
+class WalletContact implements ContactBase {
5
+ WalletContact(this.address, this.name, this.type);
6
+
7
+ @override
8
+ String address;
9
+
10
+ @override
11
+ String name;
12
+
13
+ @override
14
+ CryptoCurrency type;
15
+}
lib/entities/wallet_info.dart
+7
-3
@@ -7,7 +7,7 @@ part 'wallet_info.g.dart';
7
@HiveType(typeId: 4)
8
class WalletInfo extends HiveObject {
9
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
10
- this.timestamp, this.dirPath, this.path);
10
+ this.timestamp, this.dirPath, this.path, this.address);
11
12
factory WalletInfo.external(
13
{@required String id,
@@ -17,9 +17,10 @@ class WalletInfo extends HiveObject {
17
@required int restoreHeight,
18
@required DateTime date,
19
@required String dirPath,
20
- @required String path}) {
20
+ @required String path,
21
+ @required String address}) {
22
return WalletInfo(id, name, type, isRecovery, restoreHeight,
22
- date.millisecondsSinceEpoch ?? 0, dirPath, path);
23
+ date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
24
}
25
26
static const boxName = 'WalletInfo';
@@ -48,5 +49,8 @@ class WalletInfo extends HiveObject {
49
@HiveField(7)
50
String path;
51
52
+ @HiveField(8)
53
+ String address;
54
+
55
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
56
}
lib/entities/wallet_type.dart
+12
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/entities/crypto_currency.dart';
2
import 'package:hive/hive.dart';
3
4
part 'wallet_type.g.dart';
@@ -59,3 +60,14 @@ String walletTypeToDisplayName(WalletType type) {
60
return '';
61
}
62
}
63
+
64
+CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
65
+ switch (type) {
66
+ case WalletType.monero:
67
+ return CryptoCurrency.xmr;
68
+ case WalletType.bitcoin:
69
+ return CryptoCurrency.btc;
70
+ default:
71
+ return null;
72
+ }
73
+}
lib/main.dart
+1
-1
@@ -69,7 +69,7 @@ void main() async {
69
templates: templates,
70
exchangeTemplates: exchangeTemplates,
71
transactionDescriptions: transactionDescriptions,
72
- initialMigrationVersion: 4);
72
+ initialMigrationVersion: 5);
73
runApp(App());
74
} catch (e) {
75
runApp(MaterialApp(
lib/reactions/on_authentication_state_change.dart
+2
-2
@@ -10,14 +10,14 @@ ReactionDisposer _onAuthenticationStateChange;
10
dynamic loginError;
11
12
void startAuthenticationStateChange(AuthenticationStore authenticationStore,
13
- @required GlobalKey<NavigatorState> navigatorKey) {
13
+ GlobalKey<NavigatorState> navigatorKey) {
14
_onAuthenticationStateChange ??= autorun((_) async {
15
final state = authenticationStore.state;
16
17
if (state == AuthenticationState.installed) {
18
try {
19
await loadCurrentWallet();
20
- } catch(e) {
20
+ } catch (e) {
21
loginError = e;
22
}
23
return;
lib/reactions/on_current_wallet_change.dart
+11
-2
@@ -30,6 +30,14 @@ void startCurrentWalletChangeReaction(AppStore appStore,
30
await getIt.get<SharedPreferences>().setInt(
31
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
32
await wallet.connectToNode(node: node);
33
+
34
+ if (wallet.walletInfo.address?.isEmpty ?? true) {
35
+ wallet.walletInfo.address = wallet.address;
36
+
37
+ if (wallet.walletInfo.isInBox) {
38
+ await wallet.walletInfo.save();
39
+ }
40
+ }
41
} catch (e) {
42
print(e.toString());
43
}
@@ -39,8 +47,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
47
reaction((_) => appStore.wallet, (WalletBase wallet) async {
48
try {
49
fiatConversionStore.prices[wallet.currency] = 0;
42
- fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice(
43
- wallet.currency, settingsStore.fiatCurrency);
50
+ fiatConversionStore.prices[wallet.currency] =
51
+ await FiatConversionService.fetchPrice(
52
+ wallet.currency, settingsStore.fiatCurrency);
53
} catch (e) {
54
print(e.toString());
55
}
lib/src/screens/contact/contact_list_page.dart
+104
-97
@@ -1,3 +1,4 @@
1
+import 'package:cake_wallet/entities/contact_base.dart';
2
import 'package:cake_wallet/utils/show_bar.dart';
3
import 'package:cake_wallet/utils/show_pop_up.dart';
4
import 'package:flutter/material.dart';
@@ -61,107 +62,113 @@ class ContactListPage extends BasePage {
62
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
63
child: Observer(
64
builder: (_) {
64
- return contactListViewModel.contacts.isNotEmpty
65
- ? SectionStandardList(
66
- sectionCount: 1,
67
- context: context,
68
- itemCounter: (int sectionIndex) =>
69
- contactListViewModel.contacts.length,
70
- itemBuilder: (_, sectionIndex, index) {
71
- final contact = contactListViewModel.contacts[index];
72
- final image = _getCurrencyImage(contact.type);
73
- final content = GestureDetector(
74
- onTap: () async {
75
- if (!isEditable) {
76
- Navigator.of(context).pop(contact);
77
- return;
78
- }
79
-
80
- final isCopied = await showNameAndAddressDialog(
81
- context, contact.name, contact.address);
82
-
83
- if (isCopied != null && isCopied) {
84
- await Clipboard.setData(
85
- ClipboardData(text: contact.address));
86
- await showBar<void>(
87
- context, S.of(context).copied_to_clipboard);
88
- }
89
- },
90
- child: Container(
91
- color: Colors.transparent,
92
- padding: const EdgeInsets.only(
93
- left: 24, top: 16, bottom: 16, right: 24),
94
- child: Row(
95
- mainAxisSize: MainAxisSize.min,
96
- mainAxisAlignment: MainAxisAlignment.start,
97
- crossAxisAlignment: CrossAxisAlignment.center,
98
- children: <Widget>[
99
- image ?? Offstage(),
100
- Padding(
101
- padding: image != null
102
- ? EdgeInsets.only(left: 12)
103
- : EdgeInsets.only(left: 0),
104
- child: Text(
105
- contact.name,
106
- style: TextStyle(
107
- fontSize: 14,
108
- fontWeight: FontWeight.normal,
109
- color: Theme.of(context)
110
- .primaryTextTheme
111
- .title
112
- .color),
113
- ),
114
- )
115
- ],
116
- ),
117
- ),
118
- );
119
-
120
- return !isEditable
121
- ? content
122
- : Slidable(
123
- key: Key('${contact.key}'),
124
- actionPane: SlidableDrawerActionPane(),
125
- child: content,
126
- secondaryActions: <Widget>[
127
- IconSlideAction(
128
- caption: S.of(context).edit,
129
- color: Colors.blue,
130
- icon: Icons.edit,
131
- onTap: () async =>
132
- await Navigator.of(context).pushNamed(
133
- Routes.addressBookAddContact,
134
- arguments: contact),
135
- ),
136
- IconSlideAction(
137
- caption: S.of(context).delete,
138
- color: Colors.red,
139
- icon: CupertinoIcons.delete,
140
- onTap: () async {
141
- final isDelete =
142
- await showAlertDialog(context) ??
143
- false;
144
-
145
- if (isDelete) {
146
- await contactListViewModel
147
- .delete(contact);
148
- }
149
- },
150
- ),
151
- ]);
152
- },
153
- )
154
- : Center(
155
- child: Text(
156
- S.of(context).placeholder_contacts,
157
- textAlign: TextAlign.center,
158
- style: TextStyle(color: Colors.grey, fontSize: 14),
159
- ),
160
- );
65
+ return SectionStandardList(
66
+ context: context,
67
+ sectionCount: 2,
68
+ sectionTitleBuilder: (_, int sectionIndex) {
69
+ var title = 'Contacts';
70
+
71
+ if (sectionIndex == 0) {
72
+ title = 'My wallets';
73
+ }
74
+
75
+ return Container(
76
+ padding: EdgeInsets.only(left: 24, bottom: 20),
77
+ child: Text(title, style: TextStyle(fontSize: 36)));
78
+ },
79
+ itemCounter: (int sectionIndex) => sectionIndex == 0
80
+ ? contactListViewModel.walletContacts.length
81
+ : contactListViewModel.contacts.length,
82
+ itemBuilder: (_, sectionIndex, index) {
83
+ if (sectionIndex == 0) {
84
+ final walletInfo = contactListViewModel.walletContacts[index];
85
+ return generateRaw(context, walletInfo);
86
+ }
87
+
88
+ final contact = contactListViewModel.contacts[index];
89
+ final content = generateRaw(context, contact);
90
+
91
+ return !isEditable
92
+ ? content
93
+ : Slidable(
94
+ key: Key('${contact.key}'),
95
+ actionPane: SlidableDrawerActionPane(),
96
+ child: content,
97
+ secondaryActions: <Widget>[
98
+ IconSlideAction(
99
+ caption: S.of(context).edit,
100
+ color: Colors.blue,
101
+ icon: Icons.edit,
102
+ onTap: () async => await Navigator.of(context)
103
+ .pushNamed(Routes.addressBookAddContact,
104
+ arguments: contact),
105
+ ),
106
+ IconSlideAction(
107
+ caption: S.of(context).delete,
108
+ color: Colors.red,
109
+ icon: CupertinoIcons.delete,
110
+ onTap: () async {
111
+ final isDelete =
112
+ await showAlertDialog(context) ?? false;
113
+
114
+ if (isDelete) {
115
+ await contactListViewModel.delete(contact);
116
+ }
117
+ },
118
+ ),
119
+ ]);
120
+ },
121
+ );
122
},
123
));
124
}
125
126
+ Widget generateRaw(BuildContext context, ContactBase contact) {
127
+ final image = _getCurrencyImage(contact.type);
128
+
129
+ return GestureDetector(
130
+ onTap: () async {
131
+ if (!isEditable) {
132
+ Navigator.of(context).pop(contact);
133
+ return;
134
+ }
135
+
136
+ final isCopied = await showNameAndAddressDialog(
137
+ context, contact.name, contact.address);
138
+
139
+ if (isCopied != null && isCopied) {
140
+ await Clipboard.setData(ClipboardData(text: contact.address));
141
+ await showBar<void>(context, S.of(context).copied_to_clipboard);
142
+ }
143
+ },
144
+ child: Container(
145
+ color: Colors.transparent,
146
+ padding:
147
+ const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
148
+ child: Row(
149
+ mainAxisSize: MainAxisSize.min,
150
+ mainAxisAlignment: MainAxisAlignment.start,
151
+ crossAxisAlignment: CrossAxisAlignment.center,
152
+ children: <Widget>[
153
+ image ?? Offstage(),
154
+ Padding(
155
+ padding: image != null
156
+ ? EdgeInsets.only(left: 12)
157
+ : EdgeInsets.only(left: 0),
158
+ child: Text(
159
+ contact.name,
160
+ style: TextStyle(
161
+ fontSize: 14,
162
+ fontWeight: FontWeight.normal,
163
+ color: Theme.of(context).primaryTextTheme.title.color),
164
+ ),
165
+ )
166
+ ],
167
+ ),
168
+ ),
169
+ );
170
+ }
171
+
172
Image _getCurrencyImage(CryptoCurrency currency) {
173
Image image;
174
switch (currency) {
lib/src/widgets/address_text_field.dart
+3
-3
@@ -2,8 +2,8 @@ import 'package:flutter/services.dart';
2
import 'package:flutter/material.dart';
3
import 'package:cake_wallet/routes.dart';
4
import 'package:cake_wallet/generated/i18n.dart';
5
-import 'package:cake_wallet/entities/contact_record.dart';
5
import 'package:cake_wallet/entities/qr_scanner.dart';
6
+import 'package:cake_wallet/entities/contact_base.dart';
7
8
enum AddressTextFieldOption { paste, qrCode, addressBook }
9
@@ -42,7 +42,7 @@ class AddressTextField extends StatelessWidget {
42
final Color iconColor;
43
final TextStyle textStyle;
44
final TextStyle hintStyle;
45
- FocusNode focusNode;
45
+ final FocusNode focusNode;
46
47
@override
48
Widget build(BuildContext context) {
@@ -212,7 +212,7 @@ class AddressTextField extends StatelessWidget {
212
final contact = await Navigator.of(context, rootNavigator: true)
213
.pushNamed(Routes.pickerAddressBook);
214
215
- if (contact is ContactRecord && contact.address != null) {
215
+ if (contact is ContactBase && contact.address != null) {
216
controller.text = contact.address;
217
}
218
}
lib/src/widgets/standard_list.dart
+12
-3
@@ -113,16 +113,19 @@ class SectionStandardList extends StatelessWidget {
113
{@required this.itemCounter,
114
@required this.itemBuilder,
115
@required this.sectionCount,
116
+ this.sectionTitleBuilder,
117
this.hasTopSeparator = false,
118
BuildContext context})
119
: totalRows = transform(hasTopSeparator, context, sectionCount,
119
- itemCounter, itemBuilder);
120
+ itemCounter, itemBuilder, sectionTitleBuilder);
121
122
final int sectionCount;
123
final bool hasTopSeparator;
124
final int Function(int sectionIndex) itemCounter;
125
final Widget Function(BuildContext context, int sectionIndex, int itemIndex)
126
itemBuilder;
127
+ final Widget Function(BuildContext context, int sectionIndex)
128
+ sectionTitleBuilder;
129
final List<Widget> totalRows;
130
131
static List<Widget> transform(
@@ -131,14 +134,20 @@ class SectionStandardList extends StatelessWidget {
134
int sectionCount,
135
int Function(int sectionIndex) itemCounter,
136
Widget Function(BuildContext context, int sectionIndex, int itemIndex)
134
- itemBuilder) {
137
+ itemBuilder,
138
+ Widget Function(BuildContext context, int sectionIndex)
139
+ sectionTitleBuilder) {
140
final items = <Widget>[];
141
142
for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) {
138
- if ((sectionIndex == 0)&&(hasTopSeparator)) {
143
+ if ((sectionIndex == 0) && (hasTopSeparator)) {
144
items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24)));
145
}
146
147
+ if (sectionTitleBuilder != null) {
148
+ items.add(sectionTitleBuilder(context, sectionIndex));
149
+ }
150
+
151
final itemCount = itemCounter(sectionIndex);
152
153
for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) {
lib/view_model/contact_list/contact_list_view_model.dart
+12
-2
@@ -1,4 +1,7 @@
1
import 'dart:async';
2
+import 'package:cake_wallet/entities/wallet_contact.dart';
3
+import 'package:cake_wallet/entities/wallet_info.dart';
4
+import 'package:cake_wallet/entities/wallet_type.dart';
5
import 'package:hive/hive.dart';
6
import 'package:mobx/mobx.dart';
7
import 'package:cake_wallet/entities/contact_record.dart';
@@ -11,15 +14,22 @@ class ContactListViewModel = ContactListViewModelBase
14
with _$ContactListViewModel;
15
16
abstract class ContactListViewModelBase with Store {
14
- ContactListViewModelBase(this.contactSource)
15
- : contacts = ObservableList<ContactRecord>() {
17
+ ContactListViewModelBase(this.contactSource, this.walletInfoSource)
18
+ : contacts = ObservableList<ContactRecord>(),
19
+ walletContacts = walletInfoSource.values
20
+ .where((info) => info.address?.isNotEmpty ?? false)
21
+ .map((info) => WalletContact(
22
+ info.address, info.name, walletTypeToCryptoCurrency(info.type)))
23
+ .toList() {
24
_subscription = contactSource.bindToListWithTransform(
25
contacts, (Contact contact) => ContactRecord(contactSource, contact),
26
initialFire: true);
27
}
28
29
final Box<Contact> contactSource;
30
+ final Box<WalletInfo> walletInfoSource;
31
final ObservableList<ContactRecord> contacts;
32
+ final List<WalletContact> walletContacts;
33
StreamSubscription<BoxEvent> _subscription;
34
35
Future<void> delete(ContactRecord contact) async => contact.original.delete();
lib/view_model/wallet_creation_vm.dart
+1
@@ -50,6 +50,7 @@ abstract class WalletCreationVMBase with Store {
50
dirPath: dirPath);
51
credentials.walletInfo = walletInfo;
52
final wallet = await process(credentials);
53
+ walletInfo.address = wallet.address;
54
await _walletInfoSource.add(walletInfo);
55
_appStore.changeCurrentWallet(wallet);
56
_appStore.authenticationStore.allowed();