mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +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>(
|
||||
context: context,
|
||||
builder: (context) => DesktopDialog(
|
||||
maxHeight: 600,
|
||||
maxHeight: MediaQuery.of(context).size.height - 64,
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
|
@ -5,11 +5,16 @@ import 'package:stackwallet/providers/ui/color_theme_provider.dart';
|
|||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
||||
class BlueTextButton extends ConsumerStatefulWidget {
|
||||
const BlueTextButton({Key? key, required this.text, this.onTap})
|
||||
: super(key: key);
|
||||
const BlueTextButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.onTap,
|
||||
this.enabled = true,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final bool enabled;
|
||||
|
||||
@override
|
||||
ConsumerState<BlueTextButton> createState() => _BlueTextButtonState();
|
||||
|
@ -17,38 +22,42 @@ class BlueTextButton extends ConsumerStatefulWidget {
|
|||
|
||||
class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController controller;
|
||||
late Animation<dynamic> animation;
|
||||
AnimationController? controller;
|
||||
Animation<dynamic>? animation;
|
||||
late Color color;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
animation = ColorTween(
|
||||
begin: ref.read(colorThemeProvider.state).state.buttonTextBorderless,
|
||||
end: ref
|
||||
.read(colorThemeProvider.state)
|
||||
.state
|
||||
.buttonTextBorderless
|
||||
.withOpacity(0.4),
|
||||
).animate(controller);
|
||||
if (widget.enabled) {
|
||||
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
animation = ColorTween(
|
||||
begin: ref.read(colorThemeProvider.state).state.buttonTextBorderless,
|
||||
end: ref
|
||||
.read(colorThemeProvider.state)
|
||||
.state
|
||||
.buttonTextBorderless
|
||||
.withOpacity(0.4),
|
||||
).animate(controller!);
|
||||
|
||||
animation.addListener(() {
|
||||
setState(() {
|
||||
color = animation.value as Color;
|
||||
animation!.addListener(() {
|
||||
setState(() {
|
||||
color = animation!.value as Color;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
color = ref.read(colorThemeProvider.state).state.textSubtitle1;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -59,11 +68,13 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
text: TextSpan(
|
||||
text: widget.text,
|
||||
style: STextStyles.link2(context).copyWith(color: color),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
widget.onTap?.call();
|
||||
controller.forward().then((value) => controller.reverse());
|
||||
},
|
||||
recognizer: widget.enabled
|
||||
? (TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
widget.onTap?.call();
|
||||
controller?.forward().then((value) => controller?.reverse());
|
||||
})
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
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.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/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/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/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/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class NodeCard extends ConsumerStatefulWidget {
|
||||
const NodeCard({
|
||||
|
@ -30,6 +46,125 @@ class NodeCard extends ConsumerStatefulWidget {
|
|||
class _NodeCardState extends ConsumerState<NodeCard> {
|
||||
String _status = "Disconnected";
|
||||
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
|
||||
void initState() {
|
||||
|
@ -50,91 +185,176 @@ class _NodeCardState extends ConsumerState<NodeCard> {
|
|||
_status = "Disconnected";
|
||||
}
|
||||
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (_) => NodeOptionsSheet(
|
||||
nodeId: nodeId,
|
||||
coin: widget.coin,
|
||||
popBackToRoute: widget.popBackToRoute,
|
||||
borderColor: isDesktop
|
||||
? Theme.of(context).extension<StackColors>()!.background
|
||||
: null,
|
||||
child: ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) {
|
||||
return RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet<void>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
builder: (_) => NodeOptionsSheet(
|
||||
nodeId: nodeId,
|
||||
coin: widget.coin,
|
||||
popBackToRoute: widget.popBackToRoute,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: _node.name == DefaultNodes.defaultName
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonBackSecondary
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons
|
||||
.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
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: Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.node,
|
||||
height: 11,
|
||||
width: 14,
|
||||
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(
|
||||
padding: EdgeInsets.all(isDesktop ? 16 : 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: isDesktop ? 40 : 24,
|
||||
height: isDesktop ? 40 : 24,
|
||||
decoration: BoxDecoration(
|
||||
color: _node.name == DefaultNodes.defaultName
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark
|
||||
.buttonBackSecondary
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
.infoItemIcons
|
||||
.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.node,
|
||||
height: isDesktop ? 18 : 11,
|
||||
width: isDesktop ? 20 : 14,
|
||||
color: _node.name == DefaultNodes.defaultName
|
||||
? 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(
|
||||
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(),
|
||||
if (!isDesktop)
|
||||
SvgPicture.asset(
|
||||
Assets.svg.network,
|
||||
color: _status == "Connected"
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonBackSecondary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
_status,
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.network,
|
||||
color: _status == "Connected"
|
||||
? Theme.of(context)
|
||||
if (isDesktop)
|
||||
SvgPicture.asset(
|
||||
_advancedIsExpanded
|
||||
? Assets.svg.chevronDown
|
||||
: Assets.svg.chevronUp,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonBackSecondary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
],
|
||||
.textSubtitle1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue