diff --git a/lib/bitcoin/electrum.dart b/lib/bitcoin/electrum.dart index bd1ac09ac..5f034bc6a 100644 --- a/lib/bitcoin/electrum.dart +++ b/lib/bitcoin/electrum.dart @@ -87,7 +87,7 @@ class ElectrumClient { Future ping() async { try { - await callWithTimeout(method: 'server.ping'); + // await callWithTimeout(method: 'server.ping'); _setIsConnected(true); } on RequestFailedTimeoutException catch (_) { _setIsConnected(false); diff --git a/lib/core/contact_service.dart b/lib/core/contact_service.dart index 73a5bed27..1141a1d06 100644 --- a/lib/core/contact_service.dart +++ b/lib/core/contact_service.dart @@ -12,7 +12,7 @@ class ContactService { Future add(Contact contact) async { await contactSource.add(contact); - contactListStore.contacts.add(contact); + // contactListStore.contacts.add(contact); } Future update(Contact contact) async { diff --git a/lib/di.dart b/lib/di.dart index 7c08d9e49..1b533d69c 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -294,11 +294,13 @@ Future setup( getIt.registerFactoryParam( (Contact contact, _) => ContactViewModel( - getIt.get(), getIt.get().wallet, + contactSource, getIt.get().wallet, contact: contact)); getIt.registerFactory(() => ContactListViewModel( - getIt.get().contactListStore, getIt.get())); + getIt.get().contactListStore, + getIt.get(), + contactSource)); getIt.registerFactory( () => ContactListPage(getIt.get())); diff --git a/lib/src/domain/common/contact.dart b/lib/src/domain/common/contact.dart index faa4f8ecf..4aca925eb 100644 --- a/lib/src/domain/common/contact.dart +++ b/lib/src/domain/common/contact.dart @@ -1,11 +1,12 @@ import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:cake_wallet/src/domain/common/crypto_currency.dart'; +import 'package:cake_wallet/utils/mobx.dart'; part 'contact.g.dart'; @HiveType(typeId: 0) -class Contact extends HiveObject { +class Contact extends HiveObject with Keyable { Contact({@required this.name, @required this.address, CryptoCurrency type}) : raw = type?.raw; @@ -22,6 +23,9 @@ class Contact extends HiveObject { CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw); + @override + dynamic get keyIndex => key; + @override bool operator ==(Object o) => o is Contact && o.key == key; diff --git a/lib/src/domain/common/node.dart b/lib/src/domain/common/node.dart index b47f002e9..931eabf6c 100644 --- a/lib/src/domain/common/node.dart +++ b/lib/src/domain/common/node.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/mobx.dart'; import 'package:flutter/foundation.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; @@ -8,7 +9,7 @@ import 'package:cake_wallet/src/domain/common/digest_request.dart'; part 'node.g.dart'; @HiveType(typeId: 1) -class Node extends HiveObject { +class Node extends HiveObject with Keyable { Node( {@required this.uri, @required WalletType type, diff --git a/lib/src/screens/nodes/nodes_list_page.dart b/lib/src/screens/nodes/nodes_list_page.dart index ab715058c..b5a6dfba1 100644 --- a/lib/src/screens/nodes/nodes_list_page.dart +++ b/lib/src/screens/nodes/nodes_list_page.dart @@ -108,7 +108,7 @@ class NodeListPage extends BasePage { }); final dismissibleRow = Dismissible( - key: Key('${node.value.key}'), + key: Key('${node.keyIndex}'), confirmDismiss: (direction) async { return await showDialog( context: context, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index f27943be4..87eb8f4d9 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -44,9 +44,9 @@ abstract class SettingsStoreBase with Store { languageCode = initialLanguageCode; currentLocale = initialCurrentLocale; itemHeaders = {}; + this.nodes = ObservableMap.of(nodes); _sharedPreferences = sharedPreferences; _nodeSource = nodeSource; - _nodes = nodes; } static const currentNodeIdKey = 'current_node_id'; @@ -101,9 +101,9 @@ abstract class SettingsStoreBase with Store { SharedPreferences _sharedPreferences; Box _nodeSource; - Map _nodes; + ObservableMap nodes; - Node getCurrentNode(WalletType walletType) => _nodes[walletType]; + Node getCurrentNode(WalletType walletType) => nodes[walletType]; Future setCurrentNode(Node node, WalletType walletType) async { switch (walletType) { @@ -118,7 +118,7 @@ abstract class SettingsStoreBase with Store { break; } - _nodes[walletType] = node; + nodes[walletType] = node; } static Future load( diff --git a/lib/utils/item_cell.dart b/lib/utils/item_cell.dart new file mode 100644 index 000000000..4aca57b46 --- /dev/null +++ b/lib/utils/item_cell.dart @@ -0,0 +1,11 @@ +import 'package:flutter/foundation.dart'; +import 'package:cake_wallet/utils/mobx.dart'; + +class ItemCell with Keyable { + ItemCell(this.value, {@required this.isSelected, @required dynamic key}) { + keyIndex = key; + } + + final Item value; + final bool isSelected; +} diff --git a/lib/utils/mobx.dart b/lib/utils/mobx.dart index 0065a939d..6a8413aff 100644 --- a/lib/utils/mobx.dart +++ b/lib/utils/mobx.dart @@ -1,46 +1,90 @@ +import 'dart:async'; + +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -void connectDifferent( +mixin Keyable { + dynamic keyIndex; +} + +void connectDifferent( ObservableList source, ObservableList dest, Y Function(T) transform, {bool Function(T) filter}) { source.observe((ListChange change) { -// switch (change.type) { -// case OperationType.add: -// final _values = change.added; -// Iterable values; + change.elementChanges.forEach((change) { + switch (change.type) { + case OperationType.add: + if (filter?.call(change.newValue as T) ?? true) { + dest.add(transform(change.newValue as T)); + } + break; + case OperationType.remove: + // Hive could has equal index and key + dest.removeWhere( + (elem) => elem.keyIndex == (change.oldValue.key ?? change.index)); + break; + case OperationType.update: + for (var i = 0; i < dest.length; i++) { + final item = dest[i]; -// if (filter != null) { -// values = _values.where(filter); -// } - -// dest.addAll(values.map((e) => transform(e))); -// break; -// case OperationType.remove: -// change.removed.forEach((element) { -// dest.remove(element); -// }); - -// // dest.removeAt(change.index); -// break; -// case OperationType.update: -// // change.index -// break; -// } + if (item.keyIndex == change.newValue.key) { + dest[i] = transform(change.newValue as T); + } + } + break; + } + }); }); } -void connect(ObservableList source, ObservableList dest) { +void connect( + ObservableList source, ObservableList dest) { source.observe((ListChange change) { -// switch (change.type) { -// case OperationType.add: -// dest.addAll(change.added); -// break; -// case OperationType.remove: -// dest.removeAt(change.index); -// break; -// case OperationType.update: -// // change.index -// break; -// } + source.observe((ListChange change) { + change.elementChanges.forEach((change) { + switch (change.type) { + case OperationType.add: + // if (filter?.call(change.newValue as T) ?? true) { + dest.add(change.newValue as T); + // } + break; + case OperationType.remove: + // Hive could has equal index and key + dest.removeWhere((elem) => + elem.keyIndex == (change.oldValue.key ?? change.index)); + break; + case OperationType.update: + for (var i = 0; i < dest.length; i++) { + final item = dest[i]; + + if (item.keyIndex == change.newValue.key) { + dest[i] = change.newValue as T; + } + } + break; + } + }); + }); + }); +} + +StreamSubscription bindBox( + Box source, ObservableList dest) { + return source.watch().listen((event) { + if (event.deleted) { + dest.removeWhere((el) => el.keyIndex == event.key); + } + + final dynamic value = event.value; + + if (value is T) { + final elIndex = dest.indexWhere((el) => el.keyIndex == value.keyIndex); + + if (elIndex > -1) { + dest[elIndex] = value; + } else { + dest.add(value); + } + } }); } 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 a3f526a86..87917b25a 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -1,20 +1,34 @@ +import 'dart:async'; + +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/contact_service.dart'; import 'package:cake_wallet/store/contact_list_store.dart'; import 'package:cake_wallet/src/domain/common/contact.dart'; +import 'package:cake_wallet/utils/mobx.dart'; part 'contact_list_view_model.g.dart'; -class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel; +class ContactListViewModel = ContactListViewModelBase + with _$ContactListViewModel; abstract class ContactListViewModelBase with Store { - ContactListViewModelBase(this.addressBookStore, this.contactService); + ContactListViewModelBase( + this.addressBookStore, this.contactService, this.contactSource) { + _subscription = bindBox(contactSource, addressBookStore.contacts); + } final ContactListStore addressBookStore; final ContactService contactService; + final Box contactSource; - @computed ObservableList get contacts => addressBookStore.contacts; + StreamSubscription _subscription; + + void dispose() { + _subscription.cancel(); + } + Future delete(Contact contact) async => contactService.delete(contact); -} \ No newline at end of file +} diff --git a/lib/view_model/contact_list/contact_view_model.dart b/lib/view_model/contact_list/contact_view_model.dart index addb56606..780f5ca66 100644 --- a/lib/view_model/contact_list/contact_view_model.dart +++ b/lib/view_model/contact_list/contact_view_model.dart @@ -1,3 +1,4 @@ +import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/contact_service.dart'; @@ -10,7 +11,7 @@ part 'contact_view_model.g.dart'; class ContactViewModel = ContactViewModelBase with _$ContactViewModel; abstract class ContactViewModelBase with Store { - ContactViewModelBase(this._contactService, this._wallet, {Contact contact}) + ContactViewModelBase(this._contacts, this._wallet, {Contact contact}) : state = InitialContactViewModelState(), currencies = CryptoCurrency.all, _contact = contact { @@ -33,12 +34,14 @@ abstract class ContactViewModelBase with Store { @computed bool get isReady => - (name?.isNotEmpty ?? false) && (currency?.toString()?.isNotEmpty ?? false) - && (address?.isNotEmpty ?? false); + (name?.isNotEmpty ?? false) && + (currency?.toString()?.isNotEmpty ?? false) && + (address?.isNotEmpty ?? false); final List currencies; - final ContactService _contactService; + // final ContactService _contactService; final WalletBase _wallet; + final Box _contacts; final Contact _contact; @action @@ -57,10 +60,13 @@ abstract class ContactViewModelBase with Store { _contact.name = name; _contact.address = address; _contact.updateCryptoCurrency(currency: currency); - await _contactService.update(_contact); + await _contacts.put(_contact.key, _contact); + // await _contactService.update(_contact); } else { - await _contactService + await _contacts .add(Contact(name: name, address: address, type: currency)); + // await _contactService + // .add(Contact(name: name, address: address, type: currency)); } state = ContactSavingSuccessfully(); diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 73f2b934e..4a68d23aa 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/utils/item_cell.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -14,13 +15,6 @@ part 'node_list_view_model.g.dart'; class NodeListViewModel = NodeListViewModelBase with _$NodeListViewModel; -class ItemCell { - ItemCell(this.value, {@required this.isSelected}); - - final Item value; - final bool isSelected; -} - abstract class NodeListViewModelBase with Store { NodeListViewModelBase( this._nodeListStore, this._nodeSource, this._wallet, this._settingsStore) @@ -29,16 +23,16 @@ abstract class NodeListViewModelBase with Store { final values = _nodeListStore.nodes; nodes.clear(); nodes.addAll(values.where((Node node) => node.type == _wallet.type).map( - (Node val) => - ItemCell(val, isSelected: val.key == currentNode.key))); + (Node val) => ItemCell(val, + isSelected: val.key == currentNode.key, key: val.key))); connectDifferent( _nodeListStore.nodes, nodes, - (Node val) => - ItemCell(val, isSelected: val.key == currentNode.key), - filter: (Node val) { - return val.type == _wallet.type; - }); + (Node val) => ItemCell(val, + isSelected: val.key == currentNode.key, key: val.key), + filter: (Node val) => val.type == _wallet.type); + reaction((_) => _settingsStore.nodes[_wallet.type], + (Node _) => _updateCurrentNode()); } ObservableList> nodes; @@ -66,17 +60,18 @@ abstract class NodeListViewModelBase with Store { break; } - await _wallet.connectToNode(node: node); + await setAsCurrent(node); } Future delete(Node node) async => _nodeSource.delete(node.key); Future setAsCurrent(Node node) async { - await _wallet.connectToNode(node: node); await _settingsStore.setCurrentNode(node, _wallet.type); _updateCurrentNode(); + await _wallet.connectToNode(node: node); } + @action void _updateCurrentNode() { final currentNode = _settingsStore.getCurrentNode(_wallet.type); @@ -85,7 +80,8 @@ abstract class NodeListViewModelBase with Store { final isSelected = item.value.key == currentNode.key; if (item.isSelected != isSelected) { - nodes[i] = ItemCell(item.value, isSelected: isSelected); + nodes[i] = ItemCell(item.value, + isSelected: isSelected, key: item.keyIndex); } } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 4f2e3662c..0b02bc08a 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -67,7 +67,8 @@ abstract class WalletAddressListViewModelBase with Store { WalletType get type => _wallet.type; @computed - WalletAddressListItem get address => WalletAddressListItem(address: _wallet.address); + WalletAddressListItem get address => + WalletAddressListItem(address: _wallet.address); @computed PaymentURI get uri { @@ -100,15 +101,16 @@ abstract class WalletAddressListViewModelBase with Store { } if (wallet is BitcoinWallet) { - final bitcoinAddresses = wallet.addresses.map( - (addr) => WalletAddressListItem(name: addr.label, address: addr.address)); + final bitcoinAddresses = wallet.addresses.map((addr) => + WalletAddressListItem(name: addr.label, address: addr.address)); addressList.addAll(bitcoinAddresses); } return addressList; } - set address(WalletAddressListItem address) => _wallet.address = address.address; + set address(WalletAddressListItem address) => + _wallet.address = address.address; bool hasAccounts;