mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 20:29:57 +00:00
desktop wallet network settings expanding node cards
This commit is contained in:
parent
2afec92279
commit
e0a8f32d69
3 changed files with 329 additions and 98 deletions
|
@ -157,7 +157,7 @@ class _NetworkInfoButtonState extends ConsumerState<NetworkInfoButton> {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => DesktopDialog(
|
builder: (context) => DesktopDialog(
|
||||||
maxHeight: 600,
|
maxHeight: MediaQuery.of(context).size.height - 64,
|
||||||
maxWidth: 580,
|
maxWidth: 580,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
|
@ -5,11 +5,16 @@ import 'package:stackwallet/providers/ui/color_theme_provider.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
|
||||||
class BlueTextButton extends ConsumerStatefulWidget {
|
class BlueTextButton extends ConsumerStatefulWidget {
|
||||||
const BlueTextButton({Key? key, required this.text, this.onTap})
|
const BlueTextButton({
|
||||||
: super(key: key);
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
this.onTap,
|
||||||
|
this.enabled = true,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<BlueTextButton> createState() => _BlueTextButtonState();
|
ConsumerState<BlueTextButton> createState() => _BlueTextButtonState();
|
||||||
|
@ -17,12 +22,13 @@ class BlueTextButton extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late AnimationController controller;
|
AnimationController? controller;
|
||||||
late Animation<dynamic> animation;
|
Animation<dynamic>? animation;
|
||||||
late Color color;
|
late Color color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
if (widget.enabled) {
|
||||||
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
||||||
controller = AnimationController(
|
controller = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
|
@ -35,20 +41,23 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||||
.state
|
.state
|
||||||
.buttonTextBorderless
|
.buttonTextBorderless
|
||||||
.withOpacity(0.4),
|
.withOpacity(0.4),
|
||||||
).animate(controller);
|
).animate(controller!);
|
||||||
|
|
||||||
animation.addListener(() {
|
animation!.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
color = animation.value as Color;
|
color = animation!.value as Color;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
color = ref.read(colorThemeProvider.state).state.textSubtitle1;
|
||||||
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
controller.dispose();
|
controller?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,11 +68,13 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: widget.text,
|
text: widget.text,
|
||||||
style: STextStyles.link2(context).copyWith(color: color),
|
style: STextStyles.link2(context).copyWith(color: color),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: widget.enabled
|
||||||
|
? (TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
widget.onTap?.call();
|
widget.onTap?.call();
|
||||||
controller.forward().then((value) => controller.reverse());
|
controller?.forward().then((value) => controller?.reverse());
|
||||||
},
|
})
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,31 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stackwallet/electrumx_rpc/electrumx.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/node_details_view.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
import 'package:stackwallet/utilities/enums/flush_bar_type.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_monero_node_connection.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
|
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||||
|
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||||
|
import 'package:stackwallet/widgets/expandable.dart';
|
||||||
import 'package:stackwallet/widgets/node_options_sheet.dart';
|
import 'package:stackwallet/widgets/node_options_sheet.dart';
|
||||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class NodeCard extends ConsumerStatefulWidget {
|
class NodeCard extends ConsumerStatefulWidget {
|
||||||
const NodeCard({
|
const NodeCard({
|
||||||
|
@ -30,6 +46,125 @@ class NodeCard extends ConsumerStatefulWidget {
|
||||||
class _NodeCardState extends ConsumerState<NodeCard> {
|
class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
String _status = "Disconnected";
|
String _status = "Disconnected";
|
||||||
late final String nodeId;
|
late final String nodeId;
|
||||||
|
bool _advancedIsExpanded = true;
|
||||||
|
|
||||||
|
Future<void> _notifyWalletsOfUpdatedNode(WidgetRef ref) async {
|
||||||
|
final managers = ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.managers
|
||||||
|
.where((e) => e.coin == widget.coin);
|
||||||
|
final prefs = ref.read(prefsChangeNotifierProvider);
|
||||||
|
|
||||||
|
switch (prefs.syncType) {
|
||||||
|
case SyncingType.currentWalletOnly:
|
||||||
|
for (final manager in managers) {
|
||||||
|
if (manager.isActiveWallet) {
|
||||||
|
manager.updateNode(true);
|
||||||
|
} else {
|
||||||
|
manager.updateNode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.selectedWalletsAtStartup:
|
||||||
|
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
|
||||||
|
for (final manager in managers) {
|
||||||
|
if (walletIdsToSync.contains(manager.walletId)) {
|
||||||
|
manager.updateNode(true);
|
||||||
|
} else {
|
||||||
|
manager.updateNode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyncingType.allWalletsOnStartup:
|
||||||
|
for (final manager in managers) {
|
||||||
|
manager.updateNode(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _testConnection(
|
||||||
|
NodeModel node,
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
) async {
|
||||||
|
bool testPassed = false;
|
||||||
|
|
||||||
|
switch (widget.coin) {
|
||||||
|
case Coin.epicCash:
|
||||||
|
try {
|
||||||
|
final String uriString = "${node.host}:${node.port}/v1/version";
|
||||||
|
|
||||||
|
testPassed = await testEpicBoxNodeConnection(Uri.parse(uriString));
|
||||||
|
} 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";
|
||||||
|
|
||||||
|
testPassed = await testMoneroNodeConnection(Uri.parse(uriString));
|
||||||
|
}
|
||||||
|
} 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.bitcoinTestNet:
|
||||||
|
case Coin.firoTestNet:
|
||||||
|
case Coin.dogecoinTestNet:
|
||||||
|
case Coin.bitcoincash:
|
||||||
|
case Coin.litecoinTestNet:
|
||||||
|
case Coin.namecoin:
|
||||||
|
case Coin.bitcoincashTestnet:
|
||||||
|
final client = ElectrumX(
|
||||||
|
host: node.host,
|
||||||
|
port: node.port,
|
||||||
|
useSSL: node.useSSL,
|
||||||
|
failovers: [],
|
||||||
|
prefs: ref.read(prefsChangeNotifierProvider),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
testPassed = await client.ping();
|
||||||
|
} catch (_) {
|
||||||
|
testPassed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -50,16 +185,24 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
_status = "Disconnected";
|
_status = "Disconnected";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
return RoundedWhiteContainer(
|
return RoundedWhiteContainer(
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
child: RawMaterialButton(
|
borderColor: isDesktop
|
||||||
|
? Theme.of(context).extension<StackColors>()!.background
|
||||||
|
: null,
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: !isDesktop,
|
||||||
|
builder: (child) {
|
||||||
|
return RawMaterialButton(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
Constants.size.circularBorderRadius,
|
Constants.size.circularBorderRadius,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet<dynamic>(
|
showModalBottomSheet<void>(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => NodeOptionsSheet(
|
builder: (_) => NodeOptionsSheet(
|
||||||
|
@ -69,13 +212,77 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ConditionalParent(
|
||||||
|
condition: isDesktop,
|
||||||
|
builder: (child) {
|
||||||
|
return Expandable(
|
||||||
|
onExpandChanged: (state) {
|
||||||
|
setState(() {
|
||||||
|
_advancedIsExpanded = state == ExpandableState.expanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
header: child,
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 24,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 66,
|
||||||
|
),
|
||||||
|
BlueTextButton(
|
||||||
|
text: "Connect",
|
||||||
|
enabled: _status == "Disconnected",
|
||||||
|
onTap: () async {
|
||||||
|
final canConnect =
|
||||||
|
await _testConnection(_node, context, ref);
|
||||||
|
if (!canConnect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(nodeServiceChangeNotifierProvider)
|
||||||
|
.setPrimaryNodeFor(
|
||||||
|
coin: widget.coin,
|
||||||
|
node: _node,
|
||||||
|
shouldNotifyListeners: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _notifyWalletsOfUpdatedNode(ref);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 48,
|
||||||
|
),
|
||||||
|
BlueTextButton(
|
||||||
|
text: "Details",
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
NodeDetailsView.routeName,
|
||||||
|
arguments: Tuple3(
|
||||||
|
widget.coin,
|
||||||
|
widget.nodeId,
|
||||||
|
widget.popBackToRoute,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: EdgeInsets.all(isDesktop ? 16 : 12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 24,
|
width: isDesktop ? 40 : 24,
|
||||||
height: 24,
|
height: isDesktop ? 40 : 24,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _node.name == DefaultNodes.defaultName
|
color: _node.name == DefaultNodes.defaultName
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
|
@ -90,8 +297,8 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
Assets.svg.node,
|
Assets.svg.node,
|
||||||
height: 11,
|
height: isDesktop ? 18 : 11,
|
||||||
width: 14,
|
width: isDesktop ? 20 : 14,
|
||||||
color: _node.name == DefaultNodes.defaultName
|
color: _node.name == DefaultNodes.defaultName
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -122,6 +329,7 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (!isDesktop)
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
Assets.svg.network,
|
Assets.svg.network,
|
||||||
color: _status == "Connected"
|
color: _status == "Connected"
|
||||||
|
@ -134,10 +342,22 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
SvgPicture.asset(
|
||||||
|
_advancedIsExpanded
|
||||||
|
? Assets.svg.chevronDown
|
||||||
|
: Assets.svg.chevronUp,
|
||||||
|
width: 12,
|
||||||
|
height: 6,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textSubtitle1,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue