Added auto saving of wallet address to wallet info. Added users wallet addresses into address book.

This commit is contained in:
M 2021-01-08 20:10:37 +02:00
parent 26a30a62f0
commit bef18de7a6
16 changed files with 234 additions and 121 deletions

View file

@ -89,7 +89,6 @@ class BitcoinWalletService extends WalletService<
walletInfo: credentials.walletInfo); walletInfo: credentials.walletInfo);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
await wallet.generateNewAddresses(32);
return wallet; return wallet;
} }

View file

@ -330,7 +330,8 @@ Future setup(
(ContactRecord contact, _) => (ContactRecord contact, _) =>
ContactViewModel(contactSource, contact: contact)); ContactViewModel(contactSource, contact: contact));
getIt.registerFactory(() => ContactListViewModel(contactSource)); getIt.registerFactory(
() => ContactListViewModel(contactSource, walletInfoSource));
getIt.registerFactoryParam<ContactListPage, bool, void>( getIt.registerFactoryParam<ContactListPage, bool, void>(
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(), (bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@ -419,17 +420,17 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) => getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type))); WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void> getIt
((TransactionInfo transactionInfo, _) => TransactionDetailsViewModel( .registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
transactionInfo: transactionInfo, (TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
transactionDescriptionBox: transactionDescriptionBox, transactionInfo: transactionInfo,
settingsStore: getIt.get<SettingsStore>() transactionDescriptionBox: transactionDescriptionBox,
)); settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>( getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage( (TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionDetailsViewModel: getIt transactionDetailsViewModel:
.get<TransactionDetailsViewModel>(param1: transactionInfo))); getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactoryParam<NewWalletTypePage, getIt.registerFactoryParam<NewWalletTypePage,
void Function(BuildContext, WalletType), bool>( void Function(BuildContext, WalletType), bool>(

View file

@ -0,0 +1,9 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
abstract class ContactBase {
String name;
String address;
CryptoCurrency type;
}

View file

@ -3,21 +3,27 @@ import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/record.dart'; import 'package:cake_wallet/entities/record.dart';
import 'package:cake_wallet/entities/contact_base.dart';
part 'contact_record.g.dart'; part 'contact_record.g.dart';
class ContactRecord = ContactRecordBase with _$ContactRecord; class ContactRecord = ContactRecordBase with _$ContactRecord;
abstract class ContactRecordBase extends Record<Contact> with Store { abstract class ContactRecordBase extends Record<Contact>
with Store
implements ContactBase {
ContactRecordBase(Box<Contact> source, Contact original) ContactRecordBase(Box<Contact> source, Contact original)
: super(source, original); : super(source, original);
@override
@observable @observable
String name; String name;
@override
@observable @observable
String address; String address;
@override
@observable @observable
CryptoCurrency type; CryptoCurrency type;

View file

@ -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:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -73,6 +77,9 @@ Future defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 5:
await addAddressesForMoneroWallets(walletInfoSource);
break;
default: default:
break; break;
} }
@ -189,3 +196,27 @@ Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadElectrumServerList(); final serverList = await loadElectrumServerList();
await nodes.addAll(serverList); await nodes.addAll(serverList);
} }
Future<void> addAddressesForMoneroWallets(
Box<WalletInfo> 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());
}
});
}

View file

@ -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;
}

View file

@ -7,7 +7,7 @@ part 'wallet_info.g.dart';
@HiveType(typeId: 4) @HiveType(typeId: 4)
class WalletInfo extends HiveObject { class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight, 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( factory WalletInfo.external(
{@required String id, {@required String id,
@ -17,9 +17,10 @@ class WalletInfo extends HiveObject {
@required int restoreHeight, @required int restoreHeight,
@required DateTime date, @required DateTime date,
@required String dirPath, @required String dirPath,
@required String path}) { @required String path,
@required String address}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight, return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch ?? 0, dirPath, path); date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
} }
static const boxName = 'WalletInfo'; static const boxName = 'WalletInfo';
@ -48,5 +49,8 @@ class WalletInfo extends HiveObject {
@HiveField(7) @HiveField(7)
String path; String path;
@HiveField(8)
String address;
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
part 'wallet_type.g.dart'; part 'wallet_type.g.dart';
@ -59,3 +60,14 @@ String walletTypeToDisplayName(WalletType type) {
return ''; return '';
} }
} }
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
return CryptoCurrency.btc;
default:
return null;
}
}

View file

@ -69,7 +69,7 @@ void main() async {
templates: templates, templates: templates,
exchangeTemplates: exchangeTemplates, exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 4); initialMigrationVersion: 5);
runApp(App()); runApp(App());
} catch (e) { } catch (e) {
runApp(MaterialApp( runApp(MaterialApp(

View file

@ -10,14 +10,14 @@ ReactionDisposer _onAuthenticationStateChange;
dynamic loginError; dynamic loginError;
void startAuthenticationStateChange(AuthenticationStore authenticationStore, void startAuthenticationStateChange(AuthenticationStore authenticationStore,
@required GlobalKey<NavigatorState> navigatorKey) { GlobalKey<NavigatorState> navigatorKey) {
_onAuthenticationStateChange ??= autorun((_) async { _onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state; final state = authenticationStore.state;
if (state == AuthenticationState.installed) { if (state == AuthenticationState.installed) {
try { try {
await loadCurrentWallet(); await loadCurrentWallet();
} catch(e) { } catch (e) {
loginError = e; loginError = e;
} }
return; return;

View file

@ -30,6 +30,14 @@ void startCurrentWalletChangeReaction(AppStore appStore,
await getIt.get<SharedPreferences>().setInt( await getIt.get<SharedPreferences>().setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type)); PreferencesKey.currentWalletType, serializeToInt(wallet.type));
await wallet.connectToNode(node: node); 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) { } catch (e) {
print(e.toString()); print(e.toString());
} }
@ -39,8 +47,9 @@ void startCurrentWalletChangeReaction(AppStore appStore,
reaction((_) => appStore.wallet, (WalletBase wallet) async { reaction((_) => appStore.wallet, (WalletBase wallet) async {
try { try {
fiatConversionStore.prices[wallet.currency] = 0; fiatConversionStore.prices[wallet.currency] = 0;
fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice( fiatConversionStore.prices[wallet.currency] =
wallet.currency, settingsStore.fiatCurrency); await FiatConversionService.fetchPrice(
wallet.currency, settingsStore.fiatCurrency);
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }

View file

@ -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_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -61,107 +62,113 @@ class ContactListPage extends BasePage {
padding: EdgeInsets.only(top: 20.0, bottom: 20.0), padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer( child: Observer(
builder: (_) { builder: (_) {
return contactListViewModel.contacts.isNotEmpty return SectionStandardList(
? SectionStandardList( context: context,
sectionCount: 1, sectionCount: 2,
context: context, sectionTitleBuilder: (_, int sectionIndex) {
itemCounter: (int sectionIndex) => var title = 'Contacts';
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;
}
final isCopied = await showNameAndAddressDialog( if (sectionIndex == 0) {
context, contact.name, contact.address); title = 'My wallets';
}
if (isCopied != null && isCopied) { return Container(
await Clipboard.setData( padding: EdgeInsets.only(left: 24, bottom: 20),
ClipboardData(text: contact.address)); child: Text(title, style: TextStyle(fontSize: 36)));
await showBar<void>( },
context, S.of(context).copied_to_clipboard); itemCounter: (int sectionIndex) => sectionIndex == 0
} ? contactListViewModel.walletContacts.length
}, : contactListViewModel.contacts.length,
child: Container( itemBuilder: (_, sectionIndex, index) {
color: Colors.transparent, if (sectionIndex == 0) {
padding: const EdgeInsets.only( final walletInfo = contactListViewModel.walletContacts[index];
left: 24, top: 16, bottom: 16, right: 24), return generateRaw(context, walletInfo);
child: Row( }
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
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 !isEditable final contact = contactListViewModel.contacts[index];
? content final content = generateRaw(context, contact);
: Slidable(
key: Key('${contact.key}'),
actionPane: SlidableDrawerActionPane(),
child: content,
secondaryActions: <Widget>[
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) { return !isEditable
await contactListViewModel ? content
.delete(contact); : Slidable(
} key: Key('${contact.key}'),
}, actionPane: SlidableDrawerActionPane(),
), child: content,
]); secondaryActions: <Widget>[
}, IconSlideAction(
) caption: S.of(context).edit,
: Center( color: Colors.blue,
child: Text( icon: Icons.edit,
S.of(context).placeholder_contacts, onTap: () async => await Navigator.of(context)
textAlign: TextAlign.center, .pushNamed(Routes.addressBookAddContact,
style: TextStyle(color: Colors.grey, fontSize: 14), 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<void>(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: <Widget>[
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 _getCurrencyImage(CryptoCurrency currency) {
Image image; Image image;
switch (currency) { switch (currency) {

View file

@ -2,8 +2,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.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/qr_scanner.dart';
import 'package:cake_wallet/entities/contact_base.dart';
enum AddressTextFieldOption { paste, qrCode, addressBook } enum AddressTextFieldOption { paste, qrCode, addressBook }
@ -42,7 +42,7 @@ class AddressTextField extends StatelessWidget {
final Color iconColor; final Color iconColor;
final TextStyle textStyle; final TextStyle textStyle;
final TextStyle hintStyle; final TextStyle hintStyle;
FocusNode focusNode; final FocusNode focusNode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -212,7 +212,7 @@ class AddressTextField extends StatelessWidget {
final contact = await Navigator.of(context, rootNavigator: true) final contact = await Navigator.of(context, rootNavigator: true)
.pushNamed(Routes.pickerAddressBook); .pushNamed(Routes.pickerAddressBook);
if (contact is ContactRecord && contact.address != null) { if (contact is ContactBase && contact.address != null) {
controller.text = contact.address; controller.text = contact.address;
} }
} }

View file

@ -113,16 +113,19 @@ class SectionStandardList extends StatelessWidget {
{@required this.itemCounter, {@required this.itemCounter,
@required this.itemBuilder, @required this.itemBuilder,
@required this.sectionCount, @required this.sectionCount,
this.sectionTitleBuilder,
this.hasTopSeparator = false, this.hasTopSeparator = false,
BuildContext context}) BuildContext context})
: totalRows = transform(hasTopSeparator, context, sectionCount, : totalRows = transform(hasTopSeparator, context, sectionCount,
itemCounter, itemBuilder); itemCounter, itemBuilder, sectionTitleBuilder);
final int sectionCount; final int sectionCount;
final bool hasTopSeparator; final bool hasTopSeparator;
final int Function(int sectionIndex) itemCounter; final int Function(int sectionIndex) itemCounter;
final Widget Function(BuildContext context, int sectionIndex, int itemIndex) final Widget Function(BuildContext context, int sectionIndex, int itemIndex)
itemBuilder; itemBuilder;
final Widget Function(BuildContext context, int sectionIndex)
sectionTitleBuilder;
final List<Widget> totalRows; final List<Widget> totalRows;
static List<Widget> transform( static List<Widget> transform(
@ -131,14 +134,20 @@ class SectionStandardList extends StatelessWidget {
int sectionCount, int sectionCount,
int Function(int sectionIndex) itemCounter, int Function(int sectionIndex) itemCounter,
Widget Function(BuildContext context, int sectionIndex, int itemIndex) Widget Function(BuildContext context, int sectionIndex, int itemIndex)
itemBuilder) { itemBuilder,
Widget Function(BuildContext context, int sectionIndex)
sectionTitleBuilder) {
final items = <Widget>[]; final items = <Widget>[];
for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) {
if ((sectionIndex == 0)&&(hasTopSeparator)) { if ((sectionIndex == 0) && (hasTopSeparator)) {
items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24))); items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24)));
} }
if (sectionTitleBuilder != null) {
items.add(sectionTitleBuilder(context, sectionIndex));
}
final itemCount = itemCounter(sectionIndex); final itemCount = itemCounter(sectionIndex);
for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) {

View file

@ -1,4 +1,7 @@
import 'dart:async'; 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:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
@ -11,15 +14,22 @@ class ContactListViewModel = ContactListViewModelBase
with _$ContactListViewModel; with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store { abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource) ContactListViewModelBase(this.contactSource, this.walletInfoSource)
: contacts = ObservableList<ContactRecord>() { : contacts = ObservableList<ContactRecord>(),
walletContacts = walletInfoSource.values
.where((info) => info.address?.isNotEmpty ?? false)
.map((info) => WalletContact(
info.address, info.name, walletTypeToCryptoCurrency(info.type)))
.toList() {
_subscription = contactSource.bindToListWithTransform( _subscription = contactSource.bindToListWithTransform(
contacts, (Contact contact) => ContactRecord(contactSource, contact), contacts, (Contact contact) => ContactRecord(contactSource, contact),
initialFire: true); initialFire: true);
} }
final Box<Contact> contactSource; final Box<Contact> contactSource;
final Box<WalletInfo> walletInfoSource;
final ObservableList<ContactRecord> contacts; final ObservableList<ContactRecord> contacts;
final List<WalletContact> walletContacts;
StreamSubscription<BoxEvent> _subscription; StreamSubscription<BoxEvent> _subscription;
Future<void> delete(ContactRecord contact) async => contact.original.delete(); Future<void> delete(ContactRecord contact) async => contact.original.delete();

View file

@ -50,6 +50,7 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath); dirPath: dirPath);
credentials.walletInfo = walletInfo; credentials.walletInfo = walletInfo;
final wallet = await process(credentials); final wallet = await process(credentials);
walletInfo.address = wallet.address;
await _walletInfoSource.add(walletInfo); await _walletInfoSource.add(walletInfo);
_appStore.changeCurrentWallet(wallet); _appStore.changeCurrentWallet(wallet);
_appStore.authenticationStore.allowed(); _appStore.authenticationStore.allowed();