diff --git a/lib/bitcoin/bitcoin_wallet_service.dart b/lib/bitcoin/bitcoin_wallet_service.dart index 205abdcca..8e3035a2c 100644 --- a/lib/bitcoin/bitcoin_wallet_service.dart +++ b/lib/bitcoin/bitcoin_wallet_service.dart @@ -89,7 +89,6 @@ class BitcoinWalletService extends WalletService< walletInfo: credentials.walletInfo); await wallet.save(); await wallet.init(); - await wallet.generateNewAddresses(32); return wallet; } diff --git a/lib/di.dart b/lib/di.dart index a37f6fabe..2b337b59b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -330,7 +330,8 @@ Future setup( (ContactRecord contact, _) => ContactViewModel(contactSource, contact: contact)); - getIt.registerFactory(() => ContactListViewModel(contactSource)); + getIt.registerFactory( + () => ContactListViewModel(contactSource, walletInfoSource)); getIt.registerFactoryParam( (bool isEditable, _) => ContactListPage(getIt.get(), @@ -419,17 +420,17 @@ Future setup( getIt.registerFactoryParam((type, _) => WalletRestorePage(getIt.get(param1: type))); - getIt.registerFactoryParam - ((TransactionInfo transactionInfo, _) => TransactionDetailsViewModel( - transactionInfo: transactionInfo, - transactionDescriptionBox: transactionDescriptionBox, - settingsStore: getIt.get() - )); + getIt + .registerFactoryParam( + (TransactionInfo transactionInfo, _) => TransactionDetailsViewModel( + transactionInfo: transactionInfo, + transactionDescriptionBox: transactionDescriptionBox, + settingsStore: getIt.get())); getIt.registerFactoryParam( (TransactionInfo transactionInfo, _) => TransactionDetailsPage( - transactionDetailsViewModel: getIt - .get(param1: transactionInfo))); + transactionDetailsViewModel: + getIt.get(param1: transactionInfo))); getIt.registerFactoryParam( diff --git a/lib/entities/contact_base.dart b/lib/entities/contact_base.dart new file mode 100644 index 000000000..a80fd1c21 --- /dev/null +++ b/lib/entities/contact_base.dart @@ -0,0 +1,9 @@ +import 'package:cake_wallet/entities/crypto_currency.dart'; + +abstract class ContactBase { + String name; + + String address; + + CryptoCurrency type; +} \ No newline at end of file diff --git a/lib/entities/contact_record.dart b/lib/entities/contact_record.dart index c4f55cc5a..ff535ecb0 100644 --- a/lib/entities/contact_record.dart +++ b/lib/entities/contact_record.dart @@ -3,21 +3,27 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/record.dart'; +import 'package:cake_wallet/entities/contact_base.dart'; part 'contact_record.g.dart'; class ContactRecord = ContactRecordBase with _$ContactRecord; -abstract class ContactRecordBase extends Record with Store { +abstract class ContactRecordBase extends Record + with Store + implements ContactBase { ContactRecordBase(Box source, Contact original) : super(source, original); + @override @observable String name; + @override @observable String address; + @override @observable CryptoCurrency type; diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 94860d661..7a2eb210f 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -1,4 +1,8 @@ -import 'dart:io' show Platform; +import 'dart:io' show File, Platform; +import 'package:cake_wallet/core/key_service.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/entities/pathForWallet.dart'; +import 'package:cake_wallet/monero/monero_wallet_service.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -73,6 +77,9 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); break; + case 5: + await addAddressesForMoneroWallets(walletInfoSource); + break; default: break; } @@ -189,3 +196,27 @@ Future addBitcoinElectrumServerList({@required Box nodes}) async { final serverList = await loadElectrumServerList(); await nodes.addAll(serverList); } + +Future addAddressesForMoneroWallets( + Box walletInfoSource) async { + final moneroWalletsInfo = + walletInfoSource.values.where((info) => info.type == WalletType.monero); + moneroWalletsInfo.forEach((info) async { + try { + final walletPath = + await pathForWallet(name: info.name, type: WalletType.monero); + final addressFilePath = '$walletPath.address.txt'; + final addressFile = File(addressFilePath); + + if (!addressFile.existsSync()) { + return; + } + + final addressText = await addressFile.readAsString(); + info.address = addressText; + await info.save(); + } catch (e) { + print(e.toString()); + } + }); +} diff --git a/lib/entities/wallet_contact.dart b/lib/entities/wallet_contact.dart new file mode 100644 index 000000000..97edf2ac6 --- /dev/null +++ b/lib/entities/wallet_contact.dart @@ -0,0 +1,15 @@ +import 'package:cake_wallet/entities/contact_base.dart'; +import 'package:cake_wallet/entities/crypto_currency.dart'; + +class WalletContact implements ContactBase { + WalletContact(this.address, this.name, this.type); + + @override + String address; + + @override + String name; + + @override + CryptoCurrency type; +} diff --git a/lib/entities/wallet_info.dart b/lib/entities/wallet_info.dart index 97ae9f326..50c9bdcce 100644 --- a/lib/entities/wallet_info.dart +++ b/lib/entities/wallet_info.dart @@ -7,7 +7,7 @@ part 'wallet_info.g.dart'; @HiveType(typeId: 4) class WalletInfo extends HiveObject { WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight, - this.timestamp, this.dirPath, this.path); + this.timestamp, this.dirPath, this.path, this.address); factory WalletInfo.external( {@required String id, @@ -17,9 +17,10 @@ class WalletInfo extends HiveObject { @required int restoreHeight, @required DateTime date, @required String dirPath, - @required String path}) { + @required String path, + @required String address}) { return WalletInfo(id, name, type, isRecovery, restoreHeight, - date.millisecondsSinceEpoch ?? 0, dirPath, path); + date.millisecondsSinceEpoch ?? 0, dirPath, path, address); } static const boxName = 'WalletInfo'; @@ -48,5 +49,8 @@ class WalletInfo extends HiveObject { @HiveField(7) String path; + @HiveField(8) + String address; + DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); } diff --git a/lib/entities/wallet_type.dart b/lib/entities/wallet_type.dart index ef4027a54..3f4d5c884 100644 --- a/lib/entities/wallet_type.dart +++ b/lib/entities/wallet_type.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:hive/hive.dart'; part 'wallet_type.g.dart'; @@ -59,3 +60,14 @@ String walletTypeToDisplayName(WalletType type) { return ''; } } + +CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { + switch (type) { + case WalletType.monero: + return CryptoCurrency.xmr; + case WalletType.bitcoin: + return CryptoCurrency.btc; + default: + return null; + } +} diff --git a/lib/main.dart b/lib/main.dart index 38460fcca..9885c45f8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -69,7 +69,7 @@ void main() async { templates: templates, exchangeTemplates: exchangeTemplates, transactionDescriptions: transactionDescriptions, - initialMigrationVersion: 4); + initialMigrationVersion: 5); runApp(App()); } catch (e) { runApp(MaterialApp( diff --git a/lib/reactions/on_authentication_state_change.dart b/lib/reactions/on_authentication_state_change.dart index 20325cf5d..11ced88bd 100644 --- a/lib/reactions/on_authentication_state_change.dart +++ b/lib/reactions/on_authentication_state_change.dart @@ -10,14 +10,14 @@ ReactionDisposer _onAuthenticationStateChange; dynamic loginError; void startAuthenticationStateChange(AuthenticationStore authenticationStore, - @required GlobalKey navigatorKey) { + GlobalKey navigatorKey) { _onAuthenticationStateChange ??= autorun((_) async { final state = authenticationStore.state; if (state == AuthenticationState.installed) { try { await loadCurrentWallet(); - } catch(e) { + } catch (e) { loginError = e; } return; diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 117eafd33..ff47f0c69 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -30,6 +30,14 @@ void startCurrentWalletChangeReaction(AppStore appStore, await getIt.get().setInt( PreferencesKey.currentWalletType, serializeToInt(wallet.type)); await wallet.connectToNode(node: node); + + if (wallet.walletInfo.address?.isEmpty ?? true) { + wallet.walletInfo.address = wallet.address; + + if (wallet.walletInfo.isInBox) { + await wallet.walletInfo.save(); + } + } } catch (e) { print(e.toString()); } @@ -39,8 +47,9 @@ void startCurrentWalletChangeReaction(AppStore appStore, reaction((_) => appStore.wallet, (WalletBase wallet) async { try { fiatConversionStore.prices[wallet.currency] = 0; - fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice( - wallet.currency, settingsStore.fiatCurrency); + fiatConversionStore.prices[wallet.currency] = + await FiatConversionService.fetchPrice( + wallet.currency, settingsStore.fiatCurrency); } catch (e) { print(e.toString()); } diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index 548544679..bf2b593ee 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/contact_base.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; @@ -61,107 +62,113 @@ class ContactListPage extends BasePage { padding: EdgeInsets.only(top: 20.0, bottom: 20.0), child: Observer( builder: (_) { - return contactListViewModel.contacts.isNotEmpty - ? SectionStandardList( - sectionCount: 1, - context: context, - itemCounter: (int sectionIndex) => - contactListViewModel.contacts.length, - itemBuilder: (_, sectionIndex, index) { - final contact = contactListViewModel.contacts[index]; - final image = _getCurrencyImage(contact.type); - final content = GestureDetector( - onTap: () async { - if (!isEditable) { - Navigator.of(context).pop(contact); - return; - } + return SectionStandardList( + context: context, + sectionCount: 2, + sectionTitleBuilder: (_, int sectionIndex) { + var title = 'Contacts'; - final isCopied = await showNameAndAddressDialog( - context, contact.name, contact.address); + if (sectionIndex == 0) { + title = 'My wallets'; + } - if (isCopied != null && isCopied) { - await Clipboard.setData( - ClipboardData(text: contact.address)); - await showBar( - context, S.of(context).copied_to_clipboard); - } - }, - child: Container( - color: Colors.transparent, - padding: const EdgeInsets.only( - left: 24, top: 16, bottom: 16, right: 24), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - image ?? Offstage(), - Padding( - padding: image != null - ? EdgeInsets.only(left: 12) - : EdgeInsets.only(left: 0), - child: Text( - contact.name, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .primaryTextTheme - .title - .color), - ), - ) - ], - ), - ), - ); + return Container( + padding: EdgeInsets.only(left: 24, bottom: 20), + child: Text(title, style: TextStyle(fontSize: 36))); + }, + itemCounter: (int sectionIndex) => sectionIndex == 0 + ? contactListViewModel.walletContacts.length + : contactListViewModel.contacts.length, + itemBuilder: (_, sectionIndex, index) { + if (sectionIndex == 0) { + final walletInfo = contactListViewModel.walletContacts[index]; + return generateRaw(context, walletInfo); + } - return !isEditable - ? content - : Slidable( - key: Key('${contact.key}'), - actionPane: SlidableDrawerActionPane(), - child: content, - secondaryActions: [ - IconSlideAction( - caption: S.of(context).edit, - color: Colors.blue, - icon: Icons.edit, - onTap: () async => - await Navigator.of(context).pushNamed( - Routes.addressBookAddContact, - arguments: contact), - ), - IconSlideAction( - caption: S.of(context).delete, - color: Colors.red, - icon: CupertinoIcons.delete, - onTap: () async { - final isDelete = - await showAlertDialog(context) ?? - false; + final contact = contactListViewModel.contacts[index]; + final content = generateRaw(context, contact); - if (isDelete) { - await contactListViewModel - .delete(contact); - } - }, - ), - ]); - }, - ) - : Center( - child: Text( - S.of(context).placeholder_contacts, - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey, fontSize: 14), - ), - ); + return !isEditable + ? content + : Slidable( + key: Key('${contact.key}'), + actionPane: SlidableDrawerActionPane(), + child: content, + secondaryActions: [ + IconSlideAction( + caption: S.of(context).edit, + color: Colors.blue, + icon: Icons.edit, + onTap: () async => await Navigator.of(context) + .pushNamed(Routes.addressBookAddContact, + arguments: contact), + ), + IconSlideAction( + caption: S.of(context).delete, + color: Colors.red, + icon: CupertinoIcons.delete, + onTap: () async { + final isDelete = + await showAlertDialog(context) ?? false; + + if (isDelete) { + await contactListViewModel.delete(contact); + } + }, + ), + ]); + }, + ); }, )); } + Widget generateRaw(BuildContext context, ContactBase contact) { + final image = _getCurrencyImage(contact.type); + + return GestureDetector( + onTap: () async { + if (!isEditable) { + Navigator.of(context).pop(contact); + return; + } + + final isCopied = await showNameAndAddressDialog( + context, contact.name, contact.address); + + if (isCopied != null && isCopied) { + await Clipboard.setData(ClipboardData(text: contact.address)); + await showBar(context, S.of(context).copied_to_clipboard); + } + }, + child: Container( + color: Colors.transparent, + padding: + const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + image ?? Offstage(), + Padding( + padding: image != null + ? EdgeInsets.only(left: 12) + : EdgeInsets.only(left: 0), + child: Text( + contact.name, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).primaryTextTheme.title.color), + ), + ) + ], + ), + ), + ); + } + Image _getCurrencyImage(CryptoCurrency currency) { Image image; switch (currency) { diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 8c18d84a1..8e2c069f1 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -2,8 +2,8 @@ import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:cake_wallet/entities/contact_base.dart'; enum AddressTextFieldOption { paste, qrCode, addressBook } @@ -42,7 +42,7 @@ class AddressTextField extends StatelessWidget { final Color iconColor; final TextStyle textStyle; final TextStyle hintStyle; - FocusNode focusNode; + final FocusNode focusNode; @override Widget build(BuildContext context) { @@ -212,7 +212,7 @@ class AddressTextField extends StatelessWidget { final contact = await Navigator.of(context, rootNavigator: true) .pushNamed(Routes.pickerAddressBook); - if (contact is ContactRecord && contact.address != null) { + if (contact is ContactBase && contact.address != null) { controller.text = contact.address; } } diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index e4f82a877..9d8e117ac 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -113,16 +113,19 @@ class SectionStandardList extends StatelessWidget { {@required this.itemCounter, @required this.itemBuilder, @required this.sectionCount, + this.sectionTitleBuilder, this.hasTopSeparator = false, BuildContext context}) : totalRows = transform(hasTopSeparator, context, sectionCount, - itemCounter, itemBuilder); + itemCounter, itemBuilder, sectionTitleBuilder); final int sectionCount; final bool hasTopSeparator; final int Function(int sectionIndex) itemCounter; final Widget Function(BuildContext context, int sectionIndex, int itemIndex) itemBuilder; + final Widget Function(BuildContext context, int sectionIndex) + sectionTitleBuilder; final List totalRows; static List transform( @@ -131,14 +134,20 @@ class SectionStandardList extends StatelessWidget { int sectionCount, int Function(int sectionIndex) itemCounter, Widget Function(BuildContext context, int sectionIndex, int itemIndex) - itemBuilder) { + itemBuilder, + Widget Function(BuildContext context, int sectionIndex) + sectionTitleBuilder) { final items = []; for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { - if ((sectionIndex == 0)&&(hasTopSeparator)) { + if ((sectionIndex == 0) && (hasTopSeparator)) { items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24))); } + if (sectionTitleBuilder != null) { + items.add(sectionTitleBuilder(context, sectionIndex)); + } + final itemCount = itemCounter(sectionIndex); for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 8a59a0589..17083efe2 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -1,4 +1,7 @@ import 'dart:async'; +import 'package:cake_wallet/entities/wallet_contact.dart'; +import 'package:cake_wallet/entities/wallet_info.dart'; +import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/contact_record.dart'; @@ -11,15 +14,22 @@ class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel; abstract class ContactListViewModelBase with Store { - ContactListViewModelBase(this.contactSource) - : contacts = ObservableList() { + ContactListViewModelBase(this.contactSource, this.walletInfoSource) + : contacts = ObservableList(), + walletContacts = walletInfoSource.values + .where((info) => info.address?.isNotEmpty ?? false) + .map((info) => WalletContact( + info.address, info.name, walletTypeToCryptoCurrency(info.type))) + .toList() { _subscription = contactSource.bindToListWithTransform( contacts, (Contact contact) => ContactRecord(contactSource, contact), initialFire: true); } final Box contactSource; + final Box walletInfoSource; final ObservableList contacts; + final List walletContacts; StreamSubscription _subscription; Future delete(ContactRecord contact) async => contact.original.delete(); diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index b236fe151..58ee24087 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -50,6 +50,7 @@ abstract class WalletCreationVMBase with Store { dirPath: dirPath); credentials.walletInfo = walletInfo; final wallet = await process(credentials); + walletInfo.address = wallet.address; await _walletInfoSource.add(walletInfo); _appStore.changeCurrentWallet(wallet); _appStore.authenticationStore.allowed();