mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
desktop node management ui
This commit is contained in:
parent
14d7c443f2
commit
bed25b37f7
6 changed files with 762 additions and 388 deletions
|
@ -8,7 +8,6 @@ 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/providers/global/node_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
|
@ -20,15 +19,18 @@ 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/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
|
||||
enum AddEditNodeViewType { add, edit }
|
||||
|
||||
class AddEditNodeView extends ConsumerStatefulWidget {
|
||||
|
@ -59,6 +61,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
late final AddEditNodeViewType viewType;
|
||||
late final Coin coin;
|
||||
late final String? nodeId;
|
||||
late final bool isDesktop;
|
||||
|
||||
late bool saveEnabled;
|
||||
late bool testConnectionEnabled;
|
||||
|
@ -162,8 +165,198 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
return testPassed;
|
||||
}
|
||||
|
||||
Future<void> attemptSave() async {
|
||||
final canConnect = await _testConnection(showFlushBar: false);
|
||||
|
||||
bool? shouldSave;
|
||||
|
||||
if (!canConnect) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => isDesktop
|
||||
? DesktopDialog(
|
||||
maxWidth: 440,
|
||||
maxHeight: 300,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 32,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
Text(
|
||||
"Server currently unreachable",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
top: 16,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Text(
|
||||
"Would you like to save this node anyways?",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 2,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Cancel",
|
||||
desktopMed: true,
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop(false),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Save",
|
||||
desktopMed: true,
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop(true),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: StackDialog(
|
||||
title: "Server currently unreachable",
|
||||
message: "Would you like to save this node anyways?",
|
||||
leftButton: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context),
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((value) {
|
||||
if (value is bool && value) {
|
||||
shouldSave = true;
|
||||
} else {
|
||||
shouldSave = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!canConnect && !shouldSave!) {
|
||||
// return without saving
|
||||
return;
|
||||
}
|
||||
|
||||
final formData = ref.read(nodeFormDataProvider);
|
||||
|
||||
// strip unused path
|
||||
String address = formData.host!;
|
||||
if (coin == Coin.monero || coin == Coin.wownero || coin == Coin.epicCash) {
|
||||
if (address.startsWith("http")) {
|
||||
final uri = Uri.parse(address);
|
||||
address = "${uri.scheme}://${uri.host}";
|
||||
}
|
||||
}
|
||||
|
||||
switch (viewType) {
|
||||
case AddEditNodeViewType.add:
|
||||
NodeModel node = NodeModel(
|
||||
host: address,
|
||||
port: formData.port!,
|
||||
name: formData.name!,
|
||||
id: const Uuid().v1(),
|
||||
useSSL: formData.useSSL!,
|
||||
loginName: formData.login,
|
||||
enabled: true,
|
||||
coinName: coin.name,
|
||||
isFailover: formData.isFailover!,
|
||||
isDown: false,
|
||||
);
|
||||
|
||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||
node,
|
||||
formData.password,
|
||||
true,
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
}
|
||||
break;
|
||||
case AddEditNodeViewType.edit:
|
||||
NodeModel node = NodeModel(
|
||||
host: address,
|
||||
port: formData.port!,
|
||||
name: formData.name!,
|
||||
id: nodeId!,
|
||||
useSSL: formData.useSSL!,
|
||||
loginName: formData.login,
|
||||
enabled: true,
|
||||
coinName: coin.name,
|
||||
isFailover: formData.isFailover!,
|
||||
isDown: false,
|
||||
);
|
||||
|
||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||
node,
|
||||
formData.password,
|
||||
true,
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
ref.refresh(nodeFormDataProvider);
|
||||
|
||||
viewType = widget.viewType;
|
||||
|
@ -196,279 +389,203 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
.select((value) => value.getNodeById(id: nodeId!)))
|
||||
: null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
if (viewType == AddEditNodeViewType.edit)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("deleteNodeAppBarButtonKey"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.trash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.popUntil(context,
|
||||
ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
viewType == AddEditNodeViewType.edit ? "Edit node" : "Add node",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
if (viewType == AddEditNodeViewType.edit)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("deleteNodeAppBarButtonKey"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.trash,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.popUntil(context,
|
||||
ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
|
||||
await ref.read(nodeServiceChangeNotifierProvider).delete(
|
||||
nodeId!,
|
||||
true,
|
||||
);
|
||||
},
|
||||
await ref.read(nodeServiceChangeNotifierProvider).delete(
|
||||
nodeId!,
|
||||
true,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: 12,
|
||||
],
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight - 8),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
NodeForm(
|
||||
node: node,
|
||||
secureStore: widget.secureStore,
|
||||
readOnly: false,
|
||||
coin: widget.coin,
|
||||
onChanged: (canSave, canTest) {
|
||||
if (canSave != saveEnabled &&
|
||||
canTest != testConnectionEnabled) {
|
||||
setState(() {
|
||||
saveEnabled = canSave;
|
||||
testConnectionEnabled = canTest;
|
||||
});
|
||||
} else if (canSave != saveEnabled) {
|
||||
setState(() {
|
||||
saveEnabled = canSave;
|
||||
});
|
||||
} else if (canTest != testConnectionEnabled) {
|
||||
setState(() {
|
||||
testConnectionEnabled = canTest;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: testConnectionEnabled
|
||||
? () async {
|
||||
await _testConnection();
|
||||
}
|
||||
: null,
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonColor(context),
|
||||
child: Text(
|
||||
"Test connection",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: testConnectionEnabled
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextButton(
|
||||
style: saveEnabled
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryDisabledButtonColor(context),
|
||||
onPressed: saveEnabled
|
||||
? () async {
|
||||
final canConnect = await _testConnection(
|
||||
showFlushBar: false);
|
||||
|
||||
bool? shouldSave;
|
||||
|
||||
if (!canConnect) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
barrierDismissible: true,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Server currently unreachable",
|
||||
message:
|
||||
"Would you like to save this node anyways?",
|
||||
leftButton: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<
|
||||
StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(
|
||||
context),
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
).then((value) {
|
||||
if (value is bool && value) {
|
||||
shouldSave = true;
|
||||
} else {
|
||||
shouldSave = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!canConnect && !shouldSave!) {
|
||||
// return without saving
|
||||
return;
|
||||
}
|
||||
|
||||
final formData =
|
||||
ref.read(nodeFormDataProvider);
|
||||
|
||||
// strip unused path
|
||||
String address = formData.host!;
|
||||
if (coin == Coin.monero ||
|
||||
coin == Coin.wownero ||
|
||||
coin == Coin.epicCash) {
|
||||
if (address.startsWith("http")) {
|
||||
final uri = Uri.parse(address);
|
||||
address = "${uri.scheme}://${uri.host}";
|
||||
}
|
||||
}
|
||||
|
||||
switch (viewType) {
|
||||
case AddEditNodeViewType.add:
|
||||
NodeModel node = NodeModel(
|
||||
host: address,
|
||||
port: formData.port!,
|
||||
name: formData.name!,
|
||||
id: const Uuid().v1(),
|
||||
useSSL: formData.useSSL!,
|
||||
loginName: formData.login,
|
||||
enabled: true,
|
||||
coinName: coin.name,
|
||||
isFailover: formData.isFailover!,
|
||||
isDown: false,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider)
|
||||
.add(
|
||||
node,
|
||||
formData.password,
|
||||
true,
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(
|
||||
widget.routeOnSuccessOrDelete));
|
||||
}
|
||||
break;
|
||||
case AddEditNodeViewType.edit:
|
||||
NodeModel node = NodeModel(
|
||||
host: address,
|
||||
port: formData.port!,
|
||||
name: formData.name!,
|
||||
id: nodeId!,
|
||||
useSSL: formData.useSSL!,
|
||||
loginName: formData.login,
|
||||
enabled: true,
|
||||
coinName: coin.name,
|
||||
isFailover: formData.isFailover!,
|
||||
isDown: false,
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(
|
||||
nodeServiceChangeNotifierProvider)
|
||||
.add(
|
||||
node,
|
||||
formData.password,
|
||||
true,
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(
|
||||
widget.routeOnSuccessOrDelete));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: 12,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight - 8),
|
||||
child: IntrinsicHeight(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const AppBarBackButton(
|
||||
iconSize: 24,
|
||||
size: 40,
|
||||
),
|
||||
Text(
|
||||
"Add new node",
|
||||
style: STextStyles.desktopH3(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
top: 16,
|
||||
bottom: 32,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
NodeForm(
|
||||
node: node,
|
||||
secureStore: widget.secureStore,
|
||||
readOnly: false,
|
||||
coin: widget.coin,
|
||||
onChanged: (canSave, canTest) {
|
||||
if (canSave != saveEnabled &&
|
||||
canTest != testConnectionEnabled) {
|
||||
setState(() {
|
||||
saveEnabled = canSave;
|
||||
testConnectionEnabled = canTest;
|
||||
});
|
||||
} else if (canSave != saveEnabled) {
|
||||
setState(() {
|
||||
saveEnabled = canSave;
|
||||
});
|
||||
} else if (canTest != testConnectionEnabled) {
|
||||
setState(() {
|
||||
testConnectionEnabled = canTest;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!isDesktop) const Spacer(),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
height: 78,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Test connection",
|
||||
enabled: testConnectionEnabled,
|
||||
desktopMed: true,
|
||||
onPressed: testConnectionEnabled
|
||||
? () async {
|
||||
await _testConnection();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (isDesktop)
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Save",
|
||||
enabled: saveEnabled,
|
||||
desktopMed: true,
|
||||
onPressed: saveEnabled ? attemptSave : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (!isDesktop)
|
||||
TextButton(
|
||||
style: saveEnabled
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryDisabledButtonColor(context),
|
||||
onPressed: saveEnabled ? attemptSave : null,
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ 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/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -17,11 +18,13 @@ class CoinNodesView extends ConsumerStatefulWidget {
|
|||
const CoinNodesView({
|
||||
Key? key,
|
||||
required this.coin,
|
||||
this.rootNavigator = false,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/coinNodes";
|
||||
|
||||
final Coin coin;
|
||||
final bool rootNavigator;
|
||||
|
||||
@override
|
||||
ConsumerState<CoinNodesView> createState() => _CoinNodesViewState();
|
||||
|
@ -63,12 +66,17 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
Expanded(
|
||||
child: const DesktopDialogCloseButton(),
|
||||
child: DesktopDialogCloseButton(
|
||||
onPressedOverride: Navigator.of(
|
||||
context,
|
||||
rootNavigator: widget.rootNavigator,
|
||||
).pop,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
),
|
||||
|
@ -83,14 +91,19 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: 'Add new nodes',
|
||||
style:
|
||||
STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
text: "Add new node",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddEditNodeView.routeName,
|
||||
arguments: Tuple4(
|
||||
AddEditNodeViewType.add,
|
||||
widget.coin,
|
||||
null,
|
||||
CoinNodesView.routeName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -17,7 +17,13 @@ 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/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/delete_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class NodeDetailsView extends ConsumerStatefulWidget {
|
||||
|
@ -48,6 +54,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
late final String nodeId;
|
||||
late final String popRouteName;
|
||||
|
||||
bool _desktopReadOnly = true;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
secureStore = widget.secureStore;
|
||||
|
@ -126,130 +134,239 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
}
|
||||
|
||||
if (testPassed) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Server ping success",
|
||||
context: context,
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Server ping success",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Server unreachable",
|
||||
context: context,
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Server unreachable",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Node details",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
if (!nodeId.startsWith("default"))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("nodeDetailsEditNodeAppBarButtonKey"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.pencil,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
final node = ref.watch(nodeServiceChangeNotifierProvider
|
||||
.select((value) => value.getNodeById(id: nodeId)));
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Node details",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
if (!nodeId.startsWith("default"))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("nodeDetailsEditNodeAppBarButtonKey"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.pencil,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddEditNodeView.routeName,
|
||||
arguments: Tuple4(
|
||||
AddEditNodeViewType.edit,
|
||||
coin,
|
||||
nodeId,
|
||||
popRouteName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddEditNodeView.routeName,
|
||||
arguments: Tuple4(
|
||||
AddEditNodeViewType.edit,
|
||||
coin,
|
||||
nodeId,
|
||||
popRouteName,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
],
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final node = ref.watch(nodeServiceChangeNotifierProvider
|
||||
.select((value) => value.getNodeById(id: nodeId)));
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight - 8),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
NodeForm(
|
||||
node: node,
|
||||
secureStore: secureStore,
|
||||
readOnly: true,
|
||||
coin: coin,
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonColor(context),
|
||||
onPressed: () async {
|
||||
await _testConnection(ref, context);
|
||||
},
|
||||
child: Text(
|
||||
"Test connection",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12,
|
||||
left: 12,
|
||||
right: 12,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraints.maxHeight - 8),
|
||||
child: IntrinsicHeight(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const AppBarBackButton(
|
||||
iconSize: 24,
|
||||
size: 40,
|
||||
),
|
||||
Text(
|
||||
"Node details",
|
||||
style: STextStyles.desktopH3(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
top: 16,
|
||||
bottom: 32,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
NodeForm(
|
||||
node: node,
|
||||
secureStore: secureStore,
|
||||
readOnly: isDesktop ? _desktopReadOnly : true,
|
||||
coin: coin,
|
||||
),
|
||||
if (!isDesktop) const Spacer(),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
height: 22,
|
||||
),
|
||||
if (isDesktop)
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: _desktopReadOnly
|
||||
? null
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DeleteButton(
|
||||
label: "Delete node",
|
||||
desktopMed: true,
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await ref
|
||||
.read(nodeServiceChangeNotifierProvider)
|
||||
.delete(
|
||||
node!.id,
|
||||
true,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isDesktop && !_desktopReadOnly)
|
||||
const SizedBox(
|
||||
height: 45,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Test connection",
|
||||
desktopMed: true,
|
||||
onPressed: () async {
|
||||
await _testConnection(ref, context);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (isDesktop)
|
||||
Expanded(
|
||||
child: !nodeId.startsWith("default")
|
||||
? PrimaryButton(
|
||||
label: _desktopReadOnly ? "Edit" : "Save",
|
||||
desktopMed: true,
|
||||
onPressed: () async {
|
||||
final shouldSave = _desktopReadOnly == false;
|
||||
setState(() {
|
||||
_desktopReadOnly = !_desktopReadOnly;
|
||||
});
|
||||
|
||||
if (shouldSave) {
|
||||
// todo save node
|
||||
}
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!isDesktop)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter_svg/svg.dart';
|
|||
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart';
|
||||
import 'package:stackwallet/providers/global/node_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -167,8 +168,22 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
|
|||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => CoinNodesView(
|
||||
coin: coin,
|
||||
builder: (context) => Navigator(
|
||||
initialRoute: CoinNodesView.routeName,
|
||||
onGenerateRoute: RouteGenerator.generateRoute,
|
||||
onGenerateInitialRoutes: (_, __) {
|
||||
return [
|
||||
FadePageRoute(
|
||||
CoinNodesView(
|
||||
coin: coin,
|
||||
rootNavigator: true,
|
||||
),
|
||||
const RouteSettings(
|
||||
name: CoinNodesView.routeName,
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1468,6 +1468,20 @@ class StackColors extends ThemeExtension<StackColors> {
|
|||
}
|
||||
}
|
||||
|
||||
ButtonStyle? getDeleteEnabledButtonColor(BuildContext context) =>
|
||||
Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
textFieldErrorBG,
|
||||
),
|
||||
);
|
||||
|
||||
ButtonStyle? getDeleteDisabledButtonColor(BuildContext context) =>
|
||||
Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
buttonBackSecondaryDisabled,
|
||||
),
|
||||
);
|
||||
|
||||
ButtonStyle? getPrimaryEnabledButtonColor(BuildContext context) =>
|
||||
Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
|
|
98
lib/widgets/desktop/delete_button.dart
Normal file
98
lib/widgets/desktop/delete_button.dart
Normal file
|
@ -0,0 +1,98 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/utilities/assets.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/desktop/custom_text_button.dart';
|
||||
|
||||
class DeleteButton extends StatelessWidget {
|
||||
const DeleteButton({
|
||||
Key? key,
|
||||
this.width,
|
||||
this.height,
|
||||
this.label,
|
||||
this.onPressed,
|
||||
this.enabled = true,
|
||||
this.desktopMed = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final double? width;
|
||||
final double? height;
|
||||
final String? label;
|
||||
final VoidCallback? onPressed;
|
||||
final bool enabled;
|
||||
final bool desktopMed;
|
||||
|
||||
TextStyle getStyle(bool isDesktop, BuildContext context) {
|
||||
if (isDesktop) {
|
||||
if (desktopMed) {
|
||||
return STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: enabled
|
||||
? Theme.of(context).extension<StackColors>()!.accentColorRed
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondaryDisabled,
|
||||
);
|
||||
} else {
|
||||
return enabled
|
||||
? STextStyles.desktopButtonSecondaryEnabled(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.accentColorRed)
|
||||
: STextStyles.desktopButtonSecondaryDisabled(context);
|
||||
}
|
||||
} else {
|
||||
return STextStyles.button(context).copyWith(
|
||||
color: enabled
|
||||
? Theme.of(context).extension<StackColors>()!.accentColorRed
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondaryDisabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
return CustomTextButtonBase(
|
||||
height: desktopMed ? 56 : height,
|
||||
width: width,
|
||||
textButton: TextButton(
|
||||
onPressed: enabled ? onPressed : null,
|
||||
style: enabled
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getDeleteEnabledButtonColor(context)
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getDeleteDisabledButtonColor(context),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.trash,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: enabled
|
||||
? Theme.of(context).extension<StackColors>()!.accentColorRed
|
||||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondaryDisabled,
|
||||
),
|
||||
if (label != null)
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
if (label != null)
|
||||
Text(
|
||||
label!,
|
||||
style: getStyle(isDesktop, context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue