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();