mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
pretty big refactor for pow, still some bugs
This commit is contained in:
parent
33cb419fcc
commit
74f6f44a4b
21 changed files with 814 additions and 62 deletions
|
@ -62,8 +62,6 @@ class Node extends HiveObject with Keyable {
|
|||
@HiveField(6)
|
||||
String? socksProxyAddress;
|
||||
|
||||
bool isPowNode = false;
|
||||
|
||||
bool get isSSL => useSSL ?? false;
|
||||
|
||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||
|
|
218
cw_core/lib/pow_node.dart
Normal file
218
cw_core/lib/pow_node.dart
Normal file
|
@ -0,0 +1,218 @@
|
|||
import 'dart:io';
|
||||
import 'package:cw_core/keyable.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:http/io_client.dart' as ioc;
|
||||
|
||||
part 'pow_node.g.dart';
|
||||
|
||||
Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
|
||||
|
||||
@HiveType(typeId: PowNode.typeId)
|
||||
class PowNode extends HiveObject with Keyable {
|
||||
PowNode({
|
||||
this.login,
|
||||
this.password,
|
||||
this.useSSL,
|
||||
this.trusted = false,
|
||||
this.socksProxyAddress,
|
||||
String? uri,
|
||||
WalletType? type,
|
||||
}) {
|
||||
if (uri != null) {
|
||||
uriRaw = uri;
|
||||
}
|
||||
if (type != null) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
PowNode.fromMap(Map<String, Object?> map)
|
||||
: uriRaw = map['uri'] as String? ?? '',
|
||||
login = map['login'] as String?,
|
||||
password = map['password'] as String?,
|
||||
useSSL = map['useSSL'] as bool?,
|
||||
trusted = map['trusted'] as bool? ?? false,
|
||||
socksProxyAddress = map['socksProxyPort'] as String?;
|
||||
|
||||
static const typeId = NODE_TYPE_ID;
|
||||
static const boxName = 'Nodes';
|
||||
|
||||
@HiveField(0, defaultValue: '')
|
||||
late String uriRaw;
|
||||
|
||||
@HiveField(1)
|
||||
String? login;
|
||||
|
||||
@HiveField(2)
|
||||
String? password;
|
||||
|
||||
@HiveField(3, defaultValue: 0)
|
||||
late int typeRaw;
|
||||
|
||||
@HiveField(4)
|
||||
bool? useSSL;
|
||||
|
||||
@HiveField(5, defaultValue: false)
|
||||
bool trusted;
|
||||
|
||||
@HiveField(6)
|
||||
String? socksProxyAddress;
|
||||
|
||||
bool get isSSL => useSSL ?? false;
|
||||
|
||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||
|
||||
Uri get uri {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return Uri.http(uriRaw, '');
|
||||
case WalletType.bitcoin:
|
||||
return createUriFromElectrumAddress(uriRaw);
|
||||
case WalletType.litecoin:
|
||||
return createUriFromElectrumAddress(uriRaw);
|
||||
case WalletType.haven:
|
||||
return Uri.http(uriRaw, '');
|
||||
case WalletType.ethereum:
|
||||
return Uri.https(uriRaw, '');
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
if (uriRaw.contains("https") || uriRaw.endsWith("443") || isSSL) {
|
||||
return Uri.https(uriRaw, '');
|
||||
} else {
|
||||
return Uri.http(uriRaw, '');
|
||||
}
|
||||
default:
|
||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
other is PowNode &&
|
||||
(other.uriRaw == uriRaw &&
|
||||
other.login == login &&
|
||||
other.password == password &&
|
||||
other.typeRaw == typeRaw &&
|
||||
other.useSSL == useSSL &&
|
||||
other.trusted == trusted &&
|
||||
other.socksProxyAddress == socksProxyAddress);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
uriRaw.hashCode ^
|
||||
login.hashCode ^
|
||||
password.hashCode ^
|
||||
typeRaw.hashCode ^
|
||||
useSSL.hashCode ^
|
||||
trusted.hashCode ^
|
||||
socksProxyAddress.hashCode;
|
||||
|
||||
@override
|
||||
dynamic get keyIndex {
|
||||
_keyIndex ??= key;
|
||||
return _keyIndex;
|
||||
}
|
||||
|
||||
WalletType get type => deserializeFromInt(typeRaw);
|
||||
|
||||
set type(WalletType type) => typeRaw = serializeToInt(type);
|
||||
|
||||
dynamic _keyIndex;
|
||||
|
||||
Future<bool> requestNode() async {
|
||||
try {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return useSocksProxy
|
||||
? requestNodeWithProxy(socksProxyAddress ?? '')
|
||||
: requestMoneroNode();
|
||||
case WalletType.bitcoin:
|
||||
return requestElectrumServer();
|
||||
case WalletType.litecoin:
|
||||
return requestElectrumServer();
|
||||
case WalletType.haven:
|
||||
return requestMoneroNode();
|
||||
case WalletType.ethereum:
|
||||
return requestElectrumServer();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestMoneroNode() async {
|
||||
final path = '/json_rpc';
|
||||
final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
|
||||
final realm = 'monero-rpc';
|
||||
final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
|
||||
|
||||
try {
|
||||
final authenticatingClient = HttpClient();
|
||||
|
||||
authenticatingClient.addCredentials(
|
||||
rpcUri,
|
||||
realm,
|
||||
HttpClientDigestCredentials(login ?? '', password ?? ''),
|
||||
);
|
||||
|
||||
final http.Client client = ioc.IOClient(authenticatingClient);
|
||||
|
||||
final response = await client.post(
|
||||
rpcUri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(body),
|
||||
);
|
||||
|
||||
client.close();
|
||||
|
||||
final resBody = json.decode(response.body) as Map<String, dynamic>;
|
||||
return !(resBody['result']['offline'] as bool);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestNodeWithProxy(String proxy) async {
|
||||
if (proxy.isEmpty || !proxy.contains(':')) {
|
||||
return false;
|
||||
}
|
||||
final proxyAddress = proxy.split(':')[0];
|
||||
final proxyPort = int.parse(proxy.split(':')[1]);
|
||||
try {
|
||||
final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5));
|
||||
socket.destroy();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestElectrumServer() async {
|
||||
try {
|
||||
await SecureSocket.connect(uri.host, uri.port,
|
||||
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestEthereumServer() async {
|
||||
try {
|
||||
final response = await http.get(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
|
||||
return response.statusCode >= 200 && response.statusCode < 300;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
@ -53,7 +54,7 @@ abstract class WalletBase<
|
|||
Future<void> connectToNode({required Node node});
|
||||
|
||||
// there is a default definition here because only coins with a pow node (nano based) need to override this
|
||||
Future<void> connectToPowNode({required Node node}) async {}
|
||||
Future<void> connectToPowNode({required PowNode node}) async {}
|
||||
|
||||
Future<void> startSync();
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:cw_nano/nano_balance.dart';
|
||||
import 'package:cw_nano/nano_transaction_model.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
|
@ -20,7 +21,7 @@ class NanoClient {
|
|||
|
||||
StreamSubscription<Transfer>? subscription;
|
||||
Node? _node;
|
||||
Node? _powNode;
|
||||
PowNode? _powNode;
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
|
@ -31,7 +32,7 @@ class NanoClient {
|
|||
}
|
||||
}
|
||||
|
||||
bool connectPow(Node node) {
|
||||
bool connectPow(PowNode node) {
|
||||
try {
|
||||
_powNode = node;
|
||||
return true;
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
|
@ -152,7 +153,7 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> connectToPowNode({required Node node}) async {
|
||||
Future<void> connectToPowNode({required PowNode node}) async {
|
||||
_client.connectPow(node);
|
||||
}
|
||||
|
||||
|
|
18
lib/di.dart
18
lib/di.dart
|
@ -25,6 +25,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart
|
|||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
|
||||
|
@ -91,6 +92,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
|||
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
|
@ -225,6 +227,7 @@ final getIt = GetIt.instance;
|
|||
var _isSetupFinished = false;
|
||||
late Box<WalletInfo> _walletInfoSource;
|
||||
late Box<Node> _nodeSource;
|
||||
late Box<PowNode> _powNodeSource;
|
||||
late Box<Contact> _contactSource;
|
||||
late Box<Trade> _tradesSource;
|
||||
late Box<Template> _templates;
|
||||
|
@ -237,6 +240,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
|
|||
Future setup({
|
||||
required Box<WalletInfo> walletInfoSource,
|
||||
required Box<Node> nodeSource,
|
||||
required Box<PowNode> powNodeSource,
|
||||
required Box<Contact> contactSource,
|
||||
required Box<Trade> tradesSource,
|
||||
required Box<Template> templates,
|
||||
|
@ -248,6 +252,7 @@ Future setup({
|
|||
}) async {
|
||||
_walletInfoSource = walletInfoSource;
|
||||
_nodeSource = nodeSource;
|
||||
_powNodeSource = powNodeSource;
|
||||
_contactSource = contactSource;
|
||||
_tradesSource = tradesSource;
|
||||
_templates = templates;
|
||||
|
@ -271,7 +276,7 @@ Future setup({
|
|||
|
||||
final settingsStore = await SettingsStoreBase.load(
|
||||
nodeSource: _nodeSource,
|
||||
powNodeSource: _nodeSource,
|
||||
powNodeSource: _powNodeSource,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
// Enforce darkTheme on platforms other than mobile till the design for other themes is completed
|
||||
initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
|
||||
|
@ -284,6 +289,7 @@ Future setup({
|
|||
}
|
||||
|
||||
getIt.registerFactory<Box<Node>>(() => _nodeSource);
|
||||
getIt.registerFactory<Box<PowNode>>(() => _powNodeSource);
|
||||
|
||||
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
|
||||
getIt.registerSingleton(AuthenticationStore());
|
||||
|
@ -742,7 +748,7 @@ Future setup({
|
|||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return PowNodeListViewModel(_nodeSource, appStore);
|
||||
return PowNodeListViewModel(_powNodeSource, appStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
|
||||
|
@ -764,7 +770,7 @@ Future setup({
|
|||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditViewModel, WalletType?, void>(
|
||||
(WalletType? type, _) => PowNodeCreateOrEditViewModel(
|
||||
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
|
||||
_powNodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
|
||||
|
@ -772,6 +778,12 @@ Future setup({
|
|||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditPage, PowNode?, bool?>(
|
||||
(PowNode? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<PowNodeCreateOrEditViewModel>(),
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
|
||||
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
|
||||
settingsStore: getIt.get<AppStore>().settingsStore,
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
|||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -35,6 +36,7 @@ Future defaultSettingsMigration(
|
|||
required SharedPreferences sharedPreferences,
|
||||
required FlutterSecureStorage secureStorage,
|
||||
required Box<Node> nodes,
|
||||
required Box<PowNode> powNodes,
|
||||
required Box<WalletInfo> walletInfoSource,
|
||||
required Box<Trade> tradeSource,
|
||||
required Box<Contact> contactSource}) async {
|
||||
|
@ -43,7 +45,7 @@ Future defaultSettingsMigration(
|
|||
}
|
||||
|
||||
// check current nodes for nullability regardless of the version
|
||||
await checkCurrentNodes(nodes, sharedPreferences);
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
|
||||
final isNewInstall =
|
||||
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
|
||||
|
@ -118,7 +120,7 @@ Future defaultSettingsMigration(
|
|||
break;
|
||||
|
||||
case 12:
|
||||
await checkCurrentNodes(nodes, sharedPreferences);
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
break;
|
||||
|
||||
case 13:
|
||||
|
@ -129,13 +131,13 @@ Future defaultSettingsMigration(
|
|||
await addLitecoinElectrumServerList(nodes: nodes);
|
||||
await changeLitecoinCurrentElectrumServerToDefault(
|
||||
sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await checkCurrentNodes(nodes, sharedPreferences);
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
await addHavenNodeList(nodes: nodes);
|
||||
await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await checkCurrentNodes(nodes, sharedPreferences);
|
||||
await checkCurrentNodes(nodes, powNodes, sharedPreferences);
|
||||
break;
|
||||
|
||||
case 17:
|
||||
|
@ -160,7 +162,9 @@ Future defaultSettingsMigration(
|
|||
case 22:
|
||||
await addNanoNodeList(nodes: nodes);
|
||||
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await changeNanoCurrentPowNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await changeNanoCurrentPowNodeToDefault(
|
||||
sharedPreferences: sharedPreferences, nodes: powNodes);
|
||||
await resetPowToDefault(powNodes);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -254,9 +258,10 @@ Node? getNanoDefaultNode({required Box<Node> nodes}) {
|
|||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
|
||||
}
|
||||
|
||||
Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano && node.isPowNode == true));
|
||||
PowNode? getNanoDefaultPowNode({required Box<PowNode> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((PowNode node) => node.uriRaw == nanoDefaultPowNodeUri) ??
|
||||
nodes.values
|
||||
.firstWhereOrNull((node) => (node.type == WalletType.nano));
|
||||
}
|
||||
|
||||
Node getMoneroDefaultNode({required Box<Node> nodes}) {
|
||||
|
@ -428,7 +433,7 @@ Future<void> changeDefaultMoneroNode(
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
|
||||
Future<void> checkCurrentNodes(Box<Node> nodeSource, Box<PowNode> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final currentBitcoinElectrumSeverId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
@ -492,9 +497,8 @@ Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPre
|
|||
}
|
||||
|
||||
if (currentNanoPowNodeServer == null) {
|
||||
final node = Node(uri: nanoDefaultPowNodeUri, type: WalletType.nano);
|
||||
node.isPowNode = true;
|
||||
await nodeSource.add(node);
|
||||
final node = PowNode(uri: nanoDefaultPowNodeUri, type: WalletType.nano);
|
||||
await powNodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
|
||||
}
|
||||
}
|
||||
|
@ -577,7 +581,7 @@ Future<void> changeNanoCurrentNodeToDefault(
|
|||
}
|
||||
|
||||
Future<void> changeNanoCurrentPowNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
{required SharedPreferences sharedPreferences, required Box<PowNode> nodes}) async {
|
||||
final node = getNanoDefaultPowNode(nodes: nodes);
|
||||
final nodeId = node?.key as int? ?? 0;
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import "package:yaml/yaml.dart";
|
||||
|
@ -97,23 +98,22 @@ Future<List<Node>> loadDefaultNanoNodes() async {
|
|||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<PowNode>> loadDefaultNanoPowNodes() async {
|
||||
final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
|
||||
print(powNodesRaw);
|
||||
final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
|
||||
final nodes = <PowNode>[];
|
||||
|
||||
for (final raw in loadedPowNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
final node = PowNode.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.nano;
|
||||
node.isPowNode = true;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
for (var node in nodes) {
|
||||
print(node.uriRaw + " " + node.isPowNode.toString());
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
@ -135,3 +135,10 @@ Future resetToDefault(Box<Node> nodeSource) async {
|
|||
await nodeSource.clear();
|
||||
await nodeSource.addAll(nodes);
|
||||
}
|
||||
|
||||
Future resetPowToDefault(Box<PowNode> powNodeSource) async {
|
||||
final nanoPowNodes = await loadDefaultNanoPowNodes();
|
||||
final nodes = nanoPowNodes;
|
||||
await powNodeSource.clear();
|
||||
await powNodeSource.addAll(nodes);
|
||||
}
|
|
@ -8,6 +8,7 @@ import 'package:cake_wallet/store/yat/yat_store.dart';
|
|||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -81,6 +82,10 @@ Future<void> initializeAppConfigs() async {
|
|||
CakeHive.registerAdapter(NodeAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(PowNode.typeId)) {
|
||||
CakeHive.registerAdapter(PowNodeAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TransactionDescription.typeId)) {
|
||||
CakeHive.registerAdapter(TransactionDescriptionAdapter());
|
||||
}
|
||||
|
@ -128,6 +133,7 @@ Future<void> initializeAppConfigs() async {
|
|||
final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
|
||||
final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
|
||||
final nodes = await CakeHive.openBox<Node>(Node.boxName);
|
||||
final powNodes = await CakeHive.openBox<PowNode>(PowNode.boxName);
|
||||
final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
|
||||
TransactionDescription.boxName,
|
||||
encryptionKey: transactionDescriptionsBoxKey);
|
||||
|
@ -146,6 +152,7 @@ Future<void> initializeAppConfigs() async {
|
|||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
walletInfoSource: walletInfoSource,
|
||||
contactSource: contacts,
|
||||
tradesSource: trades,
|
||||
|
@ -163,6 +170,7 @@ Future<void> initializeAppConfigs() async {
|
|||
Future<void> initialSetup(
|
||||
{required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
required Box<PowNode> powNodes,
|
||||
required Box<WalletInfo> walletInfoSource,
|
||||
required Box<Contact> contactSource,
|
||||
required Box<Trade> tradesSource,
|
||||
|
@ -183,10 +191,12 @@ Future<void> initialSetup(
|
|||
walletInfoSource: walletInfoSource,
|
||||
contactSource: contactSource,
|
||||
tradeSource: tradesSource,
|
||||
nodes: nodes);
|
||||
nodes: nodes,
|
||||
powNodes: powNodes);
|
||||
await setup(
|
||||
walletInfoSource: walletInfoSource,
|
||||
nodeSource: nodes,
|
||||
powNodeSource: powNodes,
|
||||
contactSource: contactSource,
|
||||
tradesSource: tradesSource,
|
||||
templates: templates,
|
||||
|
|
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
|||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
|
@ -56,6 +57,7 @@ import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.
|
|||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -330,6 +332,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
builder: (_) => getIt.get<NodeCreateOrEditPage>(
|
||||
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
|
||||
|
||||
case Routes.newPowNode:
|
||||
final args = settings.arguments as Map<String, dynamic>?;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
|
||||
param1: args?['editingNode'] as PowNode?, param2: args?['isSelected'] as bool?));
|
||||
|
||||
case Routes.accountCreation:
|
||||
return CupertinoPageRoute<String>(
|
||||
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
|
||||
|
|
|
@ -20,6 +20,7 @@ class Routes {
|
|||
static const walletList = '/view_model.wallet_list';
|
||||
static const auth = '/auth';
|
||||
static const newNode = '/new_node_list';
|
||||
static const newPowNode = '/new_pow_node_list';
|
||||
static const login = '/login';
|
||||
static const splash = '/splash';
|
||||
static const accountCreation = '/account_new';
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_form.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
|
202
lib/src/screens/nodes/pow_node_create_or_edit_page.dart
Normal file
202
lib/src/screens/nodes/pow_node_create_or_edit_page.dart
Normal file
|
@ -0,0 +1,202 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_form.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
|
||||
class PowNodeCreateOrEditPage extends BasePage {
|
||||
PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_addressController = TextEditingController(),
|
||||
_portController = TextEditingController(),
|
||||
_loginController = TextEditingController(),
|
||||
_passwordController = TextEditingController() {
|
||||
reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
|
||||
if (address != _addressController.text) {
|
||||
_addressController.text = address;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
|
||||
if (port != _portController.text) {
|
||||
_portController.text = port;
|
||||
}
|
||||
});
|
||||
|
||||
if (nodeCreateOrEditViewModel.hasAuthCredentials) {
|
||||
reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
|
||||
if (login != _loginController.text) {
|
||||
_loginController.text = login;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
|
||||
if (password != _passwordController.text) {
|
||||
_passwordController.text = password;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_addressController.addListener(
|
||||
() => nodeCreateOrEditViewModel.address = _addressController.text);
|
||||
_portController.addListener(
|
||||
() => nodeCreateOrEditViewModel.port = _portController.text);
|
||||
_loginController.addListener(
|
||||
() => nodeCreateOrEditViewModel.login = _loginController.text);
|
||||
_passwordController.addListener(
|
||||
() => nodeCreateOrEditViewModel.password = _passwordController.text);
|
||||
}
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final TextEditingController _addressController;
|
||||
final TextEditingController _portController;
|
||||
final TextEditingController _loginController;
|
||||
final TextEditingController _passwordController;
|
||||
|
||||
@override
|
||||
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) => IconButton(
|
||||
onPressed: () async {
|
||||
await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
icon: Image.asset(
|
||||
'assets/images/qr_code_icon.png',
|
||||
),
|
||||
);
|
||||
|
||||
final PowNodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
||||
final PowNode? editingNode;
|
||||
final bool? isSelected;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
||||
reaction((_) => nodeCreateOrEditViewModel.connectionState,
|
||||
(ExecutionState state) {
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AlertWithOneAction(
|
||||
alertTitle: S.of(context).new_node_testing,
|
||||
alertContent: state.payload as bool
|
||||
? S.of(context).node_connection_successful
|
||||
: S.of(context).node_connection_failed,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop()));
|
||||
});
|
||||
}
|
||||
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24.0),
|
||||
content: PowNodeForm(
|
||||
formKey: _formKey,
|
||||
nodeViewModel: nodeCreateOrEditViewModel,
|
||||
editingNode: editingNode,
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
bottomSection: Observer(
|
||||
builder: (_) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0),
|
||||
child: LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle:
|
||||
S.of(context).remove_node,
|
||||
alertContent: S
|
||||
.of(context)
|
||||
.remove_node_message,
|
||||
rightButtonText:
|
||||
S.of(context).remove,
|
||||
leftButtonText:
|
||||
S.of(context).cancel,
|
||||
actionRightButton: () =>
|
||||
Navigator.pop(context, true),
|
||||
actionLeftButton: () =>
|
||||
Navigator.pop(context, false));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
if (confirmed) {
|
||||
await editingNode!.delete();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
text: S.of(context).delete,
|
||||
isDisabled: !nodeCreateOrEditViewModel.isReady ||
|
||||
(isSelected ?? false),
|
||||
color: Palette.red,
|
||||
textColor: Colors.white),
|
||||
)),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: PrimaryButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await nodeCreateOrEditViewModel.save(
|
||||
editingNode: editingNode, saveAsCurrent: isSelected ?? false);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.color!,
|
||||
textColor: Colors.white,
|
||||
isDisabled: (!nodeCreateOrEditViewModel.isReady)||
|
||||
(nodeCreateOrEditViewModel
|
||||
.connectionState is IsExecutingState),
|
||||
),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
201
lib/src/screens/nodes/widgets/pow_node_form.dart
Normal file
201
lib/src/screens/nodes/widgets/pow_node_form.dart
Normal file
|
@ -0,0 +1,201 @@
|
|||
import 'package:cake_wallet/core/node_address_validator.dart';
|
||||
import 'package:cake_wallet/core/node_port_validator.dart';
|
||||
import 'package:cake_wallet/core/socks_proxy_node_address_validator.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class PowNodeForm extends StatelessWidget {
|
||||
PowNodeForm({
|
||||
required this.nodeViewModel,
|
||||
required this.formKey,
|
||||
this.editingNode,
|
||||
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
|
||||
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
|
||||
_loginController = TextEditingController(text: editingNode?.login),
|
||||
_passwordController = TextEditingController(text: editingNode?.password),
|
||||
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
|
||||
if (editingNode != null) {
|
||||
nodeViewModel
|
||||
..setAddress((editingNode!.uri.host.toString()))
|
||||
..setPort((editingNode!.uri.port.toString()))
|
||||
..setPassword((editingNode!.password ?? ''))
|
||||
..setLogin((editingNode!.login ?? ''))
|
||||
..setSSL((editingNode!.isSSL))
|
||||
..setTrusted((editingNode!.trusted))
|
||||
..setSocksProxy((editingNode!.useSocksProxy))
|
||||
..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
|
||||
}
|
||||
if (nodeViewModel.hasAuthCredentials) {
|
||||
reaction((_) => nodeViewModel.login, (String login) {
|
||||
if (login != _loginController.text) {
|
||||
_loginController.text = login;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => nodeViewModel.password, (String password) {
|
||||
if (password != _passwordController.text) {
|
||||
_passwordController.text = password;
|
||||
}
|
||||
});
|
||||
}
|
||||
reaction((_) => nodeViewModel.address, (String address) {
|
||||
if (address != _addressController.text) {
|
||||
_addressController.text = address;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => nodeViewModel.port, (String port) {
|
||||
if (port != _portController.text) {
|
||||
_portController.text = port;
|
||||
}
|
||||
});
|
||||
|
||||
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
|
||||
_portController.addListener(() => nodeViewModel.port = _portController.text);
|
||||
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
|
||||
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
|
||||
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
|
||||
}
|
||||
|
||||
final PowNodeCreateOrEditViewModel nodeViewModel;
|
||||
final GlobalKey<FormState> formKey;
|
||||
final PowNode? editingNode;
|
||||
|
||||
final TextEditingController _addressController;
|
||||
final TextEditingController _portController;
|
||||
final TextEditingController _loginController;
|
||||
final TextEditingController _passwordController;
|
||||
final TextEditingController _socksAddressController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _addressController,
|
||||
hintText: S.of(context).node_address,
|
||||
validator: NodeAddressValidator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _portController,
|
||||
hintText: S.of(context).node_port,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||
validator: NodePortValidator(),
|
||||
))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
if (nodeViewModel.hasAuthCredentials) ...[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _loginController,
|
||||
hintText: S.of(context).login,
|
||||
))
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _passwordController,
|
||||
hintText: S.of(context).password,
|
||||
))
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: nodeViewModel.useSSL,
|
||||
onChanged: (value) => nodeViewModel.useSSL = value,
|
||||
caption: S.of(context).use_ssl,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => StandardCheckbox(
|
||||
value: nodeViewModel.trusted,
|
||||
onChanged: (value) => nodeViewModel.trusted = value,
|
||||
caption: S.of(context).trusted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
StandardCheckbox(
|
||||
value: nodeViewModel.useSocksProxy,
|
||||
onChanged: (value) {
|
||||
if (!value) {
|
||||
_socksAddressController.text = '';
|
||||
}
|
||||
nodeViewModel.useSocksProxy = value;
|
||||
},
|
||||
caption: 'SOCKS Proxy',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (nodeViewModel.useSocksProxy) ...[
|
||||
SizedBox(height: 10.0),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: BaseTextFormField(
|
||||
controller: _socksAddressController,
|
||||
hintText: '[<ip>:]<port>',
|
||||
validator: SocksProxyNodeAddressValidator(),
|
||||
))
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
)),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
18
lib/src/screens/nodes/widgets/pow_node_indicator.dart
Normal file
18
lib/src/screens/nodes/widgets/pow_node_indicator.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
|
||||
class NodeIndicator extends StatelessWidget {
|
||||
NodeIndicator({this.isLive = false});
|
||||
|
||||
final bool isLive;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 12.0,
|
||||
height: 12.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle, color: isLive ? Palette.green : Palette.red),
|
||||
);
|
||||
}
|
||||
}
|
62
lib/src/screens/nodes/widgets/pow_node_list_row.dart
Normal file
62
lib/src/screens/nodes/widgets/pow_node_list_row.dart
Normal file
|
@ -0,0 +1,62 @@
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PowNodeListRow extends StandardListRow {
|
||||
PowNodeListRow(
|
||||
{required String title,
|
||||
required this.node,
|
||||
required void Function(BuildContext context) onTap,
|
||||
required bool isSelected})
|
||||
: super(title: title, onTap: onTap, isSelected: isSelected);
|
||||
|
||||
final PowNode node;
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: node.requestNode(),
|
||||
builder: (context, snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.done:
|
||||
return NodeIndicator(isLive: (snapshot.data as bool?) ?? false);
|
||||
default:
|
||||
return NodeIndicator(isLive: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.newPowNode,
|
||||
arguments: {'editingNode': node, 'isSelected': isSelected}),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium!
|
||||
.decorationColor!),
|
||||
child: Icon(Icons.edit,
|
||||
size: 14,
|
||||
color: Theme.of(context).textTheme.headlineMedium!.color!)));
|
||||
}
|
||||
}
|
||||
|
||||
class PowNodeHeaderListRow extends StandardListRow {
|
||||
PowNodeHeaderListRow({required String title, required void Function(BuildContext context) onTap})
|
||||
: super(title: title, onTap: onTap, isSelected: false);
|
||||
|
||||
@override
|
||||
Widget buildTrailing(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 20,
|
||||
child: Icon(Icons.add,
|
||||
color: Theme.of(context).accentTextTheme!.titleMedium!.color,size: 24.0),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,11 +2,13 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -26,9 +28,9 @@ class ManagePowNodesPage extends BasePage {
|
|||
children: [
|
||||
Semantics(
|
||||
button: true,
|
||||
child: NodeHeaderListRow(
|
||||
child: PowNodeHeaderListRow(
|
||||
title: S.of(context).add_new_node,
|
||||
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newNode),
|
||||
onTap: (_) async => await Navigator.of(context).pushNamed(Routes.newPowNode),
|
||||
),
|
||||
),
|
||||
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
|
@ -45,12 +47,8 @@ class ManagePowNodesPage extends BasePage {
|
|||
},
|
||||
itemBuilder: (_, sectionIndex, index) {
|
||||
final node = nodeListViewModel.nodes[index];
|
||||
// technically not correct but the node doesn't
|
||||
// have any potentially unique attributes (keyIndex -> hashCode)
|
||||
// and it fixes the bug where the default (pow) node is not highlighted until
|
||||
// after making a selection
|
||||
final isSelected = node.hashCode == nodeListViewModel.currentNode.hashCode;
|
||||
final nodeListRow = NodeListRow(
|
||||
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
|
||||
final nodeListRow = PowNodeListRow(
|
||||
title: node.uriRaw,
|
||||
node: node,
|
||||
isSelected: isSelected,
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cake_wallet/entities/sort_balance_types.dart';
|
|||
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/themes/theme_list.dart';
|
||||
|
@ -60,6 +61,7 @@ abstract class SettingsStoreBase with Store {
|
|||
required this.appVersion,
|
||||
required this.deviceName,
|
||||
required Map<WalletType, Node> nodes,
|
||||
required Map<WalletType, PowNode> powNodes,
|
||||
required this.shouldShowYatPopup,
|
||||
required this.isBitcoinBuyEnabled,
|
||||
required this.actionlistDisplayMode,
|
||||
|
@ -82,7 +84,7 @@ abstract class SettingsStoreBase with Store {
|
|||
TransactionPriority? initialLitecoinTransactionPriority,
|
||||
TransactionPriority? initialEthereumTransactionPriority})
|
||||
: nodes = ObservableMap<WalletType, Node>.of(nodes),
|
||||
powNodes = ObservableMap<WalletType, Node>.of(nodes),
|
||||
powNodes = ObservableMap<WalletType, PowNode>.of(powNodes),
|
||||
_sharedPreferences = sharedPreferences,
|
||||
_backgroundTasks = backgroundTasks,
|
||||
fiatCurrency = initialFiatCurrency,
|
||||
|
@ -457,7 +459,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final BackgroundTasks _backgroundTasks;
|
||||
|
||||
ObservableMap<WalletType, Node> nodes;
|
||||
ObservableMap<WalletType, Node> powNodes;
|
||||
ObservableMap<WalletType, PowNode> powNodes;
|
||||
|
||||
Node getCurrentNode(WalletType walletType) {
|
||||
final node = nodes[walletType];
|
||||
|
@ -469,7 +471,7 @@ abstract class SettingsStoreBase with Store {
|
|||
return node;
|
||||
}
|
||||
|
||||
Node getCurrentPowNode(WalletType walletType) {
|
||||
PowNode getCurrentPowNode(WalletType walletType) {
|
||||
final node = powNodes[walletType];
|
||||
|
||||
if (node == null) {
|
||||
|
@ -489,7 +491,7 @@ abstract class SettingsStoreBase with Store {
|
|||
|
||||
static Future<SettingsStore> load(
|
||||
{required Box<Node> nodeSource,
|
||||
required Box<Node> powNodeSource,
|
||||
required Box<PowNode> powNodeSource,
|
||||
required bool isBitcoinBuyEnabled,
|
||||
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
|
||||
BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
|
||||
|
@ -620,7 +622,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||
|
||||
final nodes = <WalletType, Node>{};
|
||||
final powNodes = <WalletType, Node>{};
|
||||
final powNodes = <WalletType, PowNode>{};
|
||||
|
||||
if (moneroNode != null) {
|
||||
nodes[WalletType.monero] = moneroNode;
|
||||
|
@ -648,6 +650,8 @@ abstract class SettingsStoreBase with Store {
|
|||
if (nanoPowNode != null) {
|
||||
powNodes[WalletType.nano] = nanoPowNode;
|
||||
}
|
||||
print(nanoPowNode);
|
||||
print("@@@@@@@@@@@@");
|
||||
|
||||
final savedSyncMode = SyncMode.all.firstWhere((element) {
|
||||
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
|
||||
|
@ -658,6 +662,7 @@ abstract class SettingsStoreBase with Store {
|
|||
sharedPreferences: sharedPreferences,
|
||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
appVersion: packageInfo.version,
|
||||
deviceName: deviceName,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
|
|
|
@ -86,7 +86,7 @@ abstract class NodeListViewModelBase with Store {
|
|||
nodes.clear();
|
||||
_nodeSource.bindToList(
|
||||
nodes,
|
||||
filter: (val) => (val.type == _appStore.wallet!.type && val.isPowNode == false),
|
||||
filter: (val) => val.type == _appStore.wallet!.type,
|
||||
initialFire: true,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
|
@ -13,7 +14,8 @@ class PowNodeCreateOrEditViewModel = PowNodeCreateOrEditViewModelBase
|
|||
with _$PowNodeCreateOrEditViewModel;
|
||||
|
||||
abstract class PowNodeCreateOrEditViewModelBase with Store {
|
||||
PowNodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
|
||||
PowNodeCreateOrEditViewModelBase(
|
||||
this._nodeSource, this._walletType, this._settingsStore)
|
||||
: state = InitialExecutionState(),
|
||||
connectionState = InitialExecutionState(),
|
||||
useSSL = false,
|
||||
|
@ -72,7 +74,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
}
|
||||
|
||||
final WalletType _walletType;
|
||||
final Box<Node> _nodeSource;
|
||||
final Box<PowNode> _nodeSource;
|
||||
final SettingsStore _settingsStore;
|
||||
|
||||
@action
|
||||
|
@ -112,8 +114,8 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
void setSocksProxyAddress(String val) => socksProxyAddress = val;
|
||||
|
||||
@action
|
||||
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
|
||||
final node = Node(
|
||||
Future<void> save({PowNode? editingNode, bool saveAsCurrent = false}) async {
|
||||
final node = PowNode(
|
||||
uri: uri,
|
||||
type: _walletType,
|
||||
login: login,
|
||||
|
@ -121,7 +123,6 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
useSSL: useSSL,
|
||||
trusted: trusted,
|
||||
socksProxyAddress: socksProxyAddress);
|
||||
node.isPowNode = true;
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
if (editingNode != null) {
|
||||
|
@ -144,7 +145,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
|
||||
@action
|
||||
Future<void> connect() async {
|
||||
final node = Node(
|
||||
final node = PowNode(
|
||||
uri: uri,
|
||||
type: _walletType,
|
||||
login: login,
|
||||
|
@ -161,7 +162,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
Node? _existingNode(Node node) {
|
||||
PowNode? _existingNode(PowNode node) {
|
||||
final nodes = _nodeSource.values.toList();
|
||||
nodes.forEach((item) {
|
||||
item.login ??= '';
|
||||
|
@ -172,7 +173,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
void setAsCurrent(Node node) => _settingsStore.powNodes[_walletType] = node;
|
||||
void setAsCurrent(PowNode node) => _settingsStore.powNodes[_walletType] = node;
|
||||
|
||||
@action
|
||||
Future<void> scanQRCodeForNewNode() async {
|
||||
|
@ -190,7 +191,7 @@ abstract class PowNodeCreateOrEditViewModelBase with Store {
|
|||
}
|
||||
|
||||
final userInfo = uri.userInfo.split(':');
|
||||
|
||||
|
||||
if (userInfo.length < 2) {
|
||||
throw Exception('Unexpected scan QR code value: Value is invalid');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/utils/mobx.dart';
|
||||
import 'package:cw_core/pow_node.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
@ -16,7 +17,7 @@ class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewMode
|
|||
|
||||
abstract class PowNodeListViewModelBase with Store {
|
||||
PowNodeListViewModelBase(this._nodeSource, this._appStore)
|
||||
: nodes = ObservableList<Node>(),
|
||||
: nodes = ObservableList<PowNode>(),
|
||||
settingsStore = _appStore.settingsStore {
|
||||
_bindNodes();
|
||||
|
||||
|
@ -26,7 +27,7 @@ abstract class PowNodeListViewModelBase with Store {
|
|||
}
|
||||
|
||||
@computed
|
||||
Node get currentNode {
|
||||
PowNode get currentNode {
|
||||
final node = settingsStore.powNodes[_appStore.wallet!.type];
|
||||
|
||||
if (node == null) {
|
||||
|
@ -40,15 +41,15 @@ abstract class PowNodeListViewModelBase with Store {
|
|||
S.current.change_current_node(uri) +
|
||||
'${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}';
|
||||
|
||||
final ObservableList<Node> nodes;
|
||||
final ObservableList<PowNode> nodes;
|
||||
final SettingsStore settingsStore;
|
||||
final Box<Node> _nodeSource;
|
||||
final Box<PowNode> _nodeSource;
|
||||
final AppStore _appStore;
|
||||
|
||||
Future<void> reset() async {
|
||||
await resetToDefault(_nodeSource);
|
||||
await resetPowToDefault(_nodeSource);
|
||||
|
||||
Node node;
|
||||
PowNode node;
|
||||
|
||||
switch (_appStore.wallet!.type) {
|
||||
case WalletType.nano:
|
||||
|
@ -62,9 +63,9 @@ abstract class PowNodeListViewModelBase with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
Future<void> delete(Node node) async => node.delete();
|
||||
Future<void> delete(PowNode node) async => node.delete();
|
||||
|
||||
Future<void> setAsCurrent(Node node) async =>
|
||||
Future<void> setAsCurrent(PowNode node) async =>
|
||||
settingsStore.powNodes[_appStore.wallet!.type] = node;
|
||||
|
||||
@action
|
||||
|
@ -74,7 +75,7 @@ abstract class PowNodeListViewModelBase with Store {
|
|||
});
|
||||
_nodeSource.bindToList(
|
||||
nodes,
|
||||
filter: (val) => (val.type == _appStore.wallet!.type && val.isPowNode == true),
|
||||
filter: (val) => val.type == _appStore.wallet!.type,
|
||||
initialFire: true,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue