Node UI Fixes (#3275)

* UI fixes, updated icons, and temporarily revert Rescan button

* readd ping test button to node list, readd rescan to connections, fixup ui

* increase gap for node edit button

* remove rescan from connections for sp

* remove rescan from connections for mweb

---------

Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
This commit is contained in:
tuxsudo
2026-06-02 15:45:44 +02:00
committed by GitHub
co-authored by Robert Malikowski
parent e6969d868c
commit a8a70f5bb9
15 changed files with 187 additions and 134 deletions
+42 -32
View File
@@ -1,6 +1,5 @@
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class ModalHeader extends StatelessWidget {
const ModalHeader(
@@ -17,38 +16,49 @@ class ModalHeader extends StatelessWidget {
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 10,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CakeImageWidget(
imageUrl: iconPath, width: 36, height: 36),
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
message,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
spacing: 10,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CakeImageWidget(
imageUrl: iconPath, width: 36, height: 36),
Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
)),
if(bottomWidget != null) ...[
Padding(padding: EdgeInsets.symmetric(horizontal: 18), child: Container(height:1, color: Theme.of(context).colorScheme.surfaceContainerHigh),),
bottomWidget!
]
],
),
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.onSurface),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
message,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
)),
],
),
),
if (bottomWidget != null) ...[
Padding(
padding: EdgeInsets.symmetric(horizontal: 18),
child:
Container(height: 1, color: Theme.of(context).colorScheme.surfaceContainerHigh),
),
ClipRRect(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(16)),
child: bottomWidget!)
]
],
),
);
}
@@ -10,7 +10,10 @@ class NodeListRow extends StatelessWidget {
required this.onTap,
required this.onEditComplete,
required this.isSelected,
required this.isPow, this.speed});
required this.isPow,
this.speed,
this.vertical = 10.0,
this.horizontal = 12.0});
final Node node;
final bool isPow;
@@ -18,6 +21,8 @@ class NodeListRow extends StatelessWidget {
final Future<void> Function(Object?) onEditComplete;
final bool isSelected;
final NodeSpeed? speed;
final double vertical;
final double horizontal;
@override
Widget build(BuildContext context) {
@@ -26,32 +31,34 @@ class NodeListRow extends StatelessWidget {
final hasLabel = node.label != null && node.label!.isNotEmpty;
final uriText = "${node.uri.host}:${node.uri.port}";
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 12),
child: Row(
spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leading,
Expanded(
child: Column(crossAxisAlignment:CrossAxisAlignment.start,spacing:4,children: [
Row(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(hasLabel ? node.label! : uriText, style: TextStyle(fontSize: 12),),
if(badgePath != null)
CakeImageWidget(imageUrl: badgePath, colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn),)
],
),
if(hasLabel) Text(uriText, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),),
],),
),
trailing,
],
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.symmetric(vertical: vertical, horizontal: horizontal),
child: Row(
spacing: 12,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
leading,
Expanded(
child: Column(crossAxisAlignment:CrossAxisAlignment.start,spacing:4,children: [
Row(
spacing: 4,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(hasLabel ? node.label! : uriText, style: TextStyle(fontSize: 12),),
if(badgePath != null)
CakeImageWidget(imageUrl: badgePath, colorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary,BlendMode.srcIn),)
],
),
if(hasLabel) Text(uriText, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),),
],),
),
trailing,
],
),
),
),
);
@@ -71,7 +78,14 @@ class NodeListRow extends StatelessWidget {
Widget buildTrailing(BuildContext context) {
return Row(
children: [
if (speed != null) CakeImageWidget(imageUrl: speed!.iconPath, width: 24, height: 24),
if (speed != null)
CakeImageWidget(
imageUrl: Theme.of(context).brightness == Brightness.dark
? speed!.darkIconPath
: speed!.iconPath,
width: 24,
height: 24,
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
@@ -80,7 +94,7 @@ class NodeListRow extends StatelessWidget {
await onEditComplete(res);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
padding: const EdgeInsets.only(right: 8.0, left: 12.0),
child: CakeImageWidget(
imageUrl: "assets/new-ui/3dots_vertical.svg",
colorFilter:
@@ -208,6 +208,12 @@ class ConnectionSyncPage extends BasePage {
label: S.of(context).domain_looks_up,
onTap: () => Navigator.of(context).pushNamed(Routes.domainLookupsPage)
),
if(_connectionSyncViewModel.hasRescan)
ListItemRegularRow(
keyValue: "rescan",
label: S.of(context).rescan,
onTap: ()=>Navigator.of(context).pushNamed(Routes.rescan)
)
],
}
)
+60 -55
View File
@@ -71,6 +71,8 @@ class _ManageNodesPageState extends State<ManageNodesPage> {
title: S.of(context).nodes,
bottomWidget: Observer(
builder: (_) => NodeListRow(
// vertical: 0,
// horizontal: 0,
node: widget.nodeListViewModel.currentNode,
speed: widget.nodeListViewModel.nodeSpeedFor(widget.nodeListViewModel.currentNode),
onEditComplete: (res)async{
@@ -90,63 +92,66 @@ class _ManageNodesPageState extends State<ManageNodesPage> {
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(18)),
child: Observer(
builder: (BuildContext context) {
int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length;
return ListView.separated(
controller: ModalScrollController.of(context),
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: itemsCount,
separatorBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Container(
height: 1,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: Observer(
builder: (BuildContext context) {
int itemsCount = widget.nodeListViewModel.nonCurrentNodes.length;
return ListView.separated(
controller: ModalScrollController.of(context),
shrinkWrap: true,
physics: BouncingScrollPhysics(),
itemCount: itemsCount,
separatorBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Container(
height: 1,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
),
),
),
itemBuilder: (_, index) {
return Observer(
builder: (context) {
final node = widget.nodeListViewModel.nonCurrentNodes[index];
final nodeListRow = NodeListRow(
node: node,
isSelected: false,
isPow: widget.isPow,
speed: widget.nodeListViewModel.nodeSpeedFor(node),
onEditComplete: (res) async {
if(res != null && res is Node) {
widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
widget.nodeListViewModel.nodes.add(res);
}
itemBuilder: (_, index) {
return Observer(
builder: (context) {
final node = widget.nodeListViewModel.nonCurrentNodes[index];
final nodeListRow = NodeListRow(
node: node,
isSelected: false,
isPow: widget.isPow,
speed: widget.nodeListViewModel.nodeSpeedFor(node),
onEditComplete: (res) async {
if(res != null && res is Node) {
widget.nodeListViewModel.nodes.removeWhere((item)=>item.id == res.id);
widget.nodeListViewModel.nodes.add(res);
}
},
onTap: () async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent:
widget.nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await widget.nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
},
);
},
);
return nodeListRow;
},
);
},
);
},
},
onTap: () async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent:
widget.nodeListViewModel.getAlertContent(node.uriRaw),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () async {
await widget.nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
},
);
},
);
},
);
return nodeListRow;
},
);
},
);
},
),
),
),
],
@@ -19,20 +19,21 @@ part 'node_list_view_model.g.dart';
class NodeSpeed {
final String iconPath;
final String darkIconPath;
// fraction of best node ping needed to be classified as this speed.
final double treshold;
const NodeSpeed._(this.iconPath, this.treshold);
const NodeSpeed._(this.iconPath, this.darkIconPath, this.treshold);
static const disconnected = NodeSpeed._(
"assets/new-ui/node_speed_badges/disconnected.svg", 0);
"assets/new-ui/node_speed_badges/disconnected.svg", "assets/new-ui/node_speed_badges/disconnected-dark.svg", 0);
static const slow = NodeSpeed._(
"assets/new-ui/node_speed_badges/slow.svg", 0);
"assets/new-ui/node_speed_badges/slow.svg", "assets/new-ui/node_speed_badges/slow-dark.svg", 0);
static const medium = NodeSpeed._(
"assets/new-ui/node_speed_badges/medium.svg", 0.5);
"assets/new-ui/node_speed_badges/medium.svg", "assets/new-ui/node_speed_badges/medium.svg", 0.5);
static const fast = NodeSpeed._(
"assets/new-ui/node_speed_badges/fast.svg", 0.8);
"assets/new-ui/node_speed_badges/fast.svg", "assets/new-ui/node_speed_badges/fast.svg", 0.8);
static const all = [fast, medium, slow, disconnected];
}
@@ -162,6 +162,8 @@ abstract class ConnectionSyncViewModelBase with Store {
@action
void setUseBlinkProtection(bool value) => _settingsStore.useBlinkProtection = value;
bool get hasRescan => _wallet.hasRescan && _wallet.type != WalletType.bitcoin && _wallet.type != WalletType.litecoin;
@action
void setUseEtherscan(bool value) {
_settingsStore.useEtherscan = value;