stack_wallet/lib/widgets/node_options_sheet.dart
2024-01-19 15:38:17 -06:00

413 lines
14 KiB
Dart

/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart';
import 'package:stackwallet/providers/global/active_wallet_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/services/tor_service.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/sync_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/test_epic_box_connection.dart';
import 'package:stackwallet/utilities/test_eth_node_connection.dart';
import 'package:stackwallet/utilities/test_monero_node_connection.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:tuple/tuple.dart';
class NodeOptionsSheet extends ConsumerWidget {
const NodeOptionsSheet({
Key? key,
required this.nodeId,
required this.coin,
required this.popBackToRoute,
}) : super(key: key);
final String nodeId;
final Coin coin;
final String popBackToRoute;
Future<void> _notifyWalletsOfUpdatedNode(WidgetRef ref) async {
final wallets =
ref.read(pWallets).wallets.where((e) => e.info.coin == coin);
final prefs = ref.read(prefsChangeNotifierProvider);
switch (prefs.syncType) {
case SyncingType.currentWalletOnly:
for (final wallet in wallets) {
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.selectedWalletsAtStartup:
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
for (final wallet in wallets) {
if (walletIdsToSync.contains(wallet.walletId)) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
} else {
unawaited(wallet.updateNode());
}
}
break;
case SyncingType.allWalletsOnStartup:
for (final wallet in wallets) {
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
}
break;
}
}
Future<bool> _testConnection(
NodeModel node, BuildContext context, WidgetRef ref) async {
bool testPassed = false;
switch (coin) {
case Coin.epicCash:
try {
testPassed = await testEpicNodeConnection(
NodeFormData()
..host = node.host
..useSSL = node.useSSL
..port = node.port,
) !=
null;
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
}
break;
case Coin.monero:
case Coin.wownero:
try {
final uri = Uri.parse(node.host);
if (uri.scheme.startsWith("http")) {
final String path = uri.path.isEmpty ? "/json_rpc" : uri.path;
String uriString = "${uri.scheme}://${uri.host}:${node.port}$path";
final response = await testMoneroNodeConnection(
Uri.parse(uriString),
false,
);
if (response.cert != null) {
// if (mounted) {
final shouldAllowBadCert = await showBadX509CertificateDialog(
response.cert!,
response.url!,
response.port!,
context,
);
if (shouldAllowBadCert) {
final response =
await testMoneroNodeConnection(Uri.parse(uriString), true);
testPassed = response.success;
}
// }
} else {
testPassed = response.success;
}
}
} catch (e, s) {
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
}
break;
case Coin.bitcoin:
case Coin.litecoin:
case Coin.dogecoin:
case Coin.firo:
case Coin.particl:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
case Coin.bitcoincash:
case Coin.litecoinTestNet:
case Coin.namecoin:
case Coin.bitcoincashTestnet:
case Coin.eCash:
case Coin.bitcoinFrost:
case Coin.bitcoinFrostTestNet:
final client = ElectrumXClient(
host: node.host,
port: node.port,
useSSL: node.useSSL,
failovers: [],
prefs: ref.read(prefsChangeNotifierProvider),
torService: ref.read(pTorService),
);
try {
testPassed = await client.ping();
} catch (_) {
testPassed = false;
}
break;
case Coin.ethereum:
try {
testPassed = await testEthNodeConnection(node.host);
} catch (_) {
testPassed = false;
}
break;
case Coin.nano:
case Coin.banano:
case Coin.tezos:
case Coin.stellar:
case Coin.stellarTestnet:
throw UnimplementedError();
//TODO: check network/node
}
if (testPassed) {
// showFloatingFlushBar(
// type: FlushBarType.success,
// message: "Server ping success",
// context: context,
// );
} else {
unawaited(showFloatingFlushBar(
type: FlushBarType.warning,
iconAsset: Assets.svg.circleAlert,
message: "Could not connect to node",
context: context,
));
}
return testPassed;
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final maxHeight = MediaQuery.of(context).size.height * 0.60;
final node = ref.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getNodeById(id: nodeId)))!;
final status = ref
.watch(nodeServiceChangeNotifierProvider
.select((value) => value.getPrimaryNodeFor(coin: coin)))
?.id !=
nodeId
? "Disconnected"
: "Connected";
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: LimitedBox(
maxHeight: maxHeight,
child: Padding(
padding: const EdgeInsets.only(
left: 24,
right: 24,
top: 10,
bottom: 0,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
width: 60,
height: 4,
),
),
const SizedBox(
height: 36,
),
Text(
"Node options",
style: STextStyles.pageTitleH2(context),
textAlign: TextAlign.left,
),
RoundedWhiteContainer(
padding: const EdgeInsets.symmetric(vertical: 38),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: node.id
.startsWith(DefaultNodes.defaultNodeIdPrefix)
? Theme.of(context)
.extension<StackColors>()!
.textSubtitle4
: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons
.withOpacity(0.2),
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: SvgPicture.asset(
Assets.svg.node,
height: 15,
width: 19,
color: node.id.startsWith(
DefaultNodes.defaultNodeIdPrefix)
? Theme.of(context)
.extension<StackColors>()!
.accentColorDark
: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
),
),
const SizedBox(
width: 12,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
node.name,
style: STextStyles.titleBold12(context),
),
const SizedBox(
height: 2,
),
Text(
status,
style: STextStyles.label(context),
),
],
),
const Spacer(),
SvgPicture.asset(
Assets.svg.network,
color: status == "Connected"
? Theme.of(context)
.extension<StackColors>()!
.accentColorGreen
: Theme.of(context)
.extension<StackColors>()!
.buttonBackSecondary,
width: 18,
),
],
),
),
Row(
children: [
// if (!node.id.startsWith("default"))
Expanded(
child: TextButton(
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
onPressed: () {
Navigator.pop(context);
Navigator.of(context).pushNamed(
NodeDetailsView.routeName,
arguments: Tuple3(
coin,
node.id,
popBackToRoute,
),
);
},
child: Text(
"Details",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
),
// if (!node.id.startsWith("default"))
const SizedBox(
width: 12,
),
Expanded(
child: TextButton(
style: status == "Connected"
? Theme.of(context)
.extension<StackColors>()!
.getPrimaryDisabledButtonStyle(context)
: Theme.of(context)
.extension<StackColors>()!
.getPrimaryEnabledButtonStyle(context),
onPressed: status == "Connected"
? null
: () async {
final canConnect =
await _testConnection(node, context, ref);
if (!canConnect) {
return;
}
await ref
.read(nodeServiceChangeNotifierProvider)
.setPrimaryNodeFor(
coin: coin,
node: node,
shouldNotifyListeners: true,
);
await _notifyWalletsOfUpdatedNode(ref);
},
child: Text(
// status == "Connected" ? "Disconnect" : "Connect",
"Connect",
style: STextStyles.button(context),
),
),
),
],
),
const SizedBox(
height: 24,
),
],
),
),
),
),
);
}
}