mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-07 12:48:28 +08:00
feat: 优化Server顶部
This commit is contained in:
+23
-392
@@ -6476,7 +6476,7 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "MMCh0",
|
||||
"x": 2911,
|
||||
"x": 2837,
|
||||
"y": 964,
|
||||
"name": "服务器列表页",
|
||||
"clip": true,
|
||||
@@ -9162,8 +9162,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "nw654",
|
||||
"x": 4201,
|
||||
"y": 964,
|
||||
"x": 4716,
|
||||
"y": 904,
|
||||
"name": "文件页(右键菜单)",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -9316,38 +9316,6 @@
|
||||
"gap": 6,
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "AYouP",
|
||||
"fill": "$accent-soft",
|
||||
"cornerRadius": "$radius-chip",
|
||||
"gap": 3,
|
||||
"padding": [
|
||||
2,
|
||||
7
|
||||
],
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "GXLC2",
|
||||
"width": 9,
|
||||
"height": 9,
|
||||
"iconFontName": "wifi",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$primary"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "A1OJB9",
|
||||
"fill": "$primary",
|
||||
"content": "切换",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 10,
|
||||
"fontWeight": "700"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "D4Byt",
|
||||
@@ -9362,32 +9330,6 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "GJXE4",
|
||||
"gap": 12,
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "oQVWD",
|
||||
"width": 22,
|
||||
"height": 22,
|
||||
"iconFontName": "search",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-secondary"
|
||||
},
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "F0OmPE",
|
||||
"width": 22,
|
||||
"height": 22,
|
||||
"iconFontName": "refresh-cw",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-secondary"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -10997,8 +10939,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "sYMaF",
|
||||
"x": 4631,
|
||||
"y": 964,
|
||||
"x": 5146,
|
||||
"y": 904,
|
||||
"name": "代码编辑页",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -12264,8 +12206,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "izXm3",
|
||||
"x": 5061,
|
||||
"y": 964,
|
||||
"x": 5576,
|
||||
"y": 904,
|
||||
"name": "图片预览页",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -12928,8 +12870,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "M93CZ",
|
||||
"x": 5491,
|
||||
"y": 964,
|
||||
"x": 6006,
|
||||
"y": 904,
|
||||
"name": "影片预览页",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -14081,8 +14023,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "AGTU4",
|
||||
"x": 5921,
|
||||
"y": 964,
|
||||
"x": 6436,
|
||||
"y": 904,
|
||||
"name": "代码编辑页(AI 助手)",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -15917,8 +15859,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "DdRKa",
|
||||
"x": 6351,
|
||||
"y": 964,
|
||||
"x": 6866,
|
||||
"y": 904,
|
||||
"name": "代码编辑页(差异视图)",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -18458,8 +18400,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "zbuYi",
|
||||
"x": 6781,
|
||||
"y": 964,
|
||||
"x": 7296,
|
||||
"y": 904,
|
||||
"name": "文件页(网格视图)",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -20492,8 +20434,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "TEpnn",
|
||||
"x": 7258,
|
||||
"y": 944,
|
||||
"x": 3866,
|
||||
"y": 884,
|
||||
"name": "文件页(未连接)",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -20559,101 +20501,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "oNTgO",
|
||||
"width": "fill_container",
|
||||
"height": 56,
|
||||
"gap": 10,
|
||||
"padding": [
|
||||
0,
|
||||
16
|
||||
],
|
||||
"justifyContent": "space_between",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "CR68D",
|
||||
"width": "fill_container",
|
||||
"height": 44,
|
||||
"fill": "$surface-card",
|
||||
"cornerRadius": "$radius-control",
|
||||
"stroke": {
|
||||
"thickness": 1.5,
|
||||
"fill": "$border-strong"
|
||||
},
|
||||
"padding": [
|
||||
0,
|
||||
14
|
||||
],
|
||||
"justifyContent": "space_between",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "E1B8b",
|
||||
"gap": 10,
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "TkoMk",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"iconFontName": "server",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-muted"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "KiMlC",
|
||||
"fill": "$font-muted",
|
||||
"content": "请选择服务器进行连接",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "500"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "K2OJS",
|
||||
"width": 18,
|
||||
"height": 18,
|
||||
"iconFontName": "chevron-down",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-secondary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "N2Oxi",
|
||||
"width": 36,
|
||||
"height": 36,
|
||||
"fill": "$surface-card",
|
||||
"cornerRadius": 10,
|
||||
"stroke": {
|
||||
"thickness": 1,
|
||||
"fill": "$border"
|
||||
},
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "JO3m0",
|
||||
"width": 20,
|
||||
"height": 20,
|
||||
"iconFontName": "ellipsis",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-secondary"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "ONO4q",
|
||||
@@ -20662,10 +20509,10 @@
|
||||
"layout": "vertical",
|
||||
"gap": 20,
|
||||
"padding": [
|
||||
24,
|
||||
160,
|
||||
16,
|
||||
12,
|
||||
16
|
||||
15
|
||||
],
|
||||
"children": [
|
||||
{
|
||||
@@ -20673,11 +20520,9 @@
|
||||
"id": "P4O3GA",
|
||||
"width": "fill_container",
|
||||
"height": 348,
|
||||
"fill": "$surface-card",
|
||||
"cornerRadius": "$radius-card",
|
||||
"stroke": {
|
||||
"thickness": 1,
|
||||
"fill": "$border"
|
||||
"thickness": 1
|
||||
},
|
||||
"layout": "vertical",
|
||||
"gap": 18,
|
||||
@@ -20772,7 +20617,7 @@
|
||||
"type": "text",
|
||||
"id": "zW3XE",
|
||||
"fill": "$font-primary",
|
||||
"content": "未连接服务器",
|
||||
"content": "SFTP文件管理",
|
||||
"textAlign": "center",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 20,
|
||||
@@ -20835,41 +20680,6 @@
|
||||
"fill": "$font-on-primary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "N3v9b",
|
||||
"gap": 6,
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "CYc4G",
|
||||
"width": 12,
|
||||
"height": 12,
|
||||
"iconFontName": "history",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-muted"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "TM5Sa",
|
||||
"fill": "$font-muted",
|
||||
"content": "最近连接: 腾讯云·香港-WebServer",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 11,
|
||||
"fontWeight": "500"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "iVo9o",
|
||||
"fill": "$primary",
|
||||
"content": "重新连接",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 11,
|
||||
"fontWeight": "700"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21041,8 +20851,8 @@
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "fbx8l",
|
||||
"x": 7688,
|
||||
"y": 944,
|
||||
"x": 4296,
|
||||
"y": 884,
|
||||
"name": "服务器下拉选择弹窗",
|
||||
"clip": true,
|
||||
"width": 390,
|
||||
@@ -21311,115 +21121,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "jBTgm",
|
||||
"width": "fill_container",
|
||||
"height": 64,
|
||||
"fill": "$primary-soft",
|
||||
"cornerRadius": "$radius-control",
|
||||
"stroke": {
|
||||
"thickness": 1.5,
|
||||
"fill": "$accent"
|
||||
},
|
||||
"gap": 12,
|
||||
"padding": [
|
||||
0,
|
||||
14
|
||||
],
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "ellipse",
|
||||
"id": "uNabj",
|
||||
"fill": "$success",
|
||||
"width": 8,
|
||||
"height": 8
|
||||
},
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "mBZtf",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"iconFontName": "server",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$primary"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "M1JR47",
|
||||
"width": "fill_container",
|
||||
"layout": "vertical",
|
||||
"gap": 4,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "I6hxiX",
|
||||
"fill": "$font-primary",
|
||||
"textGrowth": "fixed-width",
|
||||
"width": "fill_container",
|
||||
"content": "本地开发机",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "q4uIt7",
|
||||
"width": "fill_container",
|
||||
"gap": 8,
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "cEZY6",
|
||||
"fill": "$font-muted",
|
||||
"content": "192.168.1.42",
|
||||
"fontFamily": "JetBrains Mono",
|
||||
"fontSize": 11,
|
||||
"fontWeight": "normal"
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "DjBJO",
|
||||
"fill": "$surface-card-strong",
|
||||
"cornerRadius": 999,
|
||||
"stroke": {
|
||||
"thickness": 1,
|
||||
"fill": "$border"
|
||||
},
|
||||
"padding": [
|
||||
1,
|
||||
6
|
||||
],
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "eBTDi",
|
||||
"fill": "$font-secondary",
|
||||
"content": "开发",
|
||||
"fontFamily": "JetBrains Mono",
|
||||
"fontSize": 10,
|
||||
"fontWeight": "normal"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "A0ntpA",
|
||||
"width": 18,
|
||||
"height": 18,
|
||||
"iconFontName": "check",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$primary"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "JUfem",
|
||||
@@ -21905,76 +21606,6 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "jFloq",
|
||||
"width": "fill_container",
|
||||
"gap": 12,
|
||||
"padding": [
|
||||
12,
|
||||
20,
|
||||
0,
|
||||
20
|
||||
],
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "IMxuI",
|
||||
"width": "fill_container",
|
||||
"height": 48,
|
||||
"cornerRadius": "$radius-control",
|
||||
"stroke": {
|
||||
"thickness": 1,
|
||||
"fill": "$border-strong"
|
||||
},
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "DvZD9",
|
||||
"fill": "$font-secondary",
|
||||
"content": "取消",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "Fi3GS",
|
||||
"width": "fill_container",
|
||||
"height": 48,
|
||||
"fill": "$primary",
|
||||
"cornerRadius": "$radius-control",
|
||||
"gap": 8,
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "mRYqe",
|
||||
"width": 16,
|
||||
"height": 16,
|
||||
"iconFontName": "plug-zap",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "$font-on-primary"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "i0iKG",
|
||||
"fill": "$font-on-primary",
|
||||
"content": "连接选中",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "600"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
setState(() => _connectingIds.add(server.id));
|
||||
final SshConnectionConfig config;
|
||||
try {
|
||||
config = await ref.read(serverRepositoryProvider).fetchSshConfig(server.id);
|
||||
config = await ref
|
||||
.read(serverRepositoryProvider)
|
||||
.fetchSshConfig(server.id);
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
if (error is UnauthorizedFailure) {
|
||||
@@ -71,8 +73,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)
|
||||
.trf('servers.fetchSshFailed', [error.toString()]),
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
).trf('servers.fetchSshFailed', [error.toString()]),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -88,9 +91,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
}
|
||||
|
||||
void _openShell() {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const TerminalShellPage()),
|
||||
);
|
||||
Navigator.of(
|
||||
context,
|
||||
).push(MaterialPageRoute(builder: (_) => const TerminalShellPage()));
|
||||
}
|
||||
|
||||
Future<void> _openForm({ServerModel? server}) async {
|
||||
@@ -114,8 +117,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
Future<void> delete() async {
|
||||
setDialogState(() => deleting = true);
|
||||
try {
|
||||
final message =
|
||||
await ref.read(serverRepositoryProvider).deleteHost(server.id);
|
||||
final message = await ref
|
||||
.read(serverRepositoryProvider)
|
||||
.deleteHost(server.id);
|
||||
if (!mounted) return;
|
||||
setState(() => _expandedServerIds.remove(server.id));
|
||||
await _refresh();
|
||||
@@ -123,9 +127,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
if (dialogContext.mounted) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
}
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(message)));
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
if (error is UnauthorizedFailure) {
|
||||
@@ -166,8 +170,9 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: _ServerPalette.danger,
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor:
|
||||
_ServerPalette.danger.withValues(alpha: 0.62),
|
||||
disabledBackgroundColor: _ServerPalette.danger.withValues(
|
||||
alpha: 0.62,
|
||||
),
|
||||
disabledForegroundColor: Colors.white,
|
||||
),
|
||||
onPressed: deleting ? null : delete,
|
||||
@@ -252,7 +257,6 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
final hostsAsync = ref.watch(hostListProvider);
|
||||
final groupsAsync = ref.watch(groupListProvider);
|
||||
final manager = ref.watch(terminalSessionManagerProvider);
|
||||
final l = AppLocalizations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _ServerPalette.canvas,
|
||||
@@ -279,36 +283,17 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: _ServerPalette.accent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'>_',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
l.tr('servers.title'),
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.text,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
width: 36,
|
||||
height: 36,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
|
||||
child: Image.asset('assets/logo_v2_01.png', fit: BoxFit.cover),
|
||||
),
|
||||
const Spacer(),
|
||||
_HeaderIconButton(
|
||||
tooltip:
|
||||
_searchVisible ? l.tr('common.closeSearch') : l.tr('common.search'),
|
||||
tooltip: _searchVisible
|
||||
? l.tr('common.closeSearch')
|
||||
: l.tr('common.search'),
|
||||
icon: _searchVisible ? Icons.close : Icons.search,
|
||||
onPressed: _toggleSearch,
|
||||
),
|
||||
@@ -381,45 +366,29 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
duration: const Duration(milliseconds: 220),
|
||||
switchInCurve: Curves.easeOut,
|
||||
switchOutCurve: Curves.easeIn,
|
||||
transitionBuilder: (child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: _searchVisible
|
||||
? Padding(
|
||||
key: const ValueKey('search-field'),
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: TextField(
|
||||
controller: _searchCtrl,
|
||||
autofocus: true,
|
||||
cursorColor: _ServerPalette.primary,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
hintText: l.tr('servers.searchHint'),
|
||||
filled: true,
|
||||
fillColor: _ServerPalette.card,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
color: _ServerPalette.border,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
controller: _searchCtrl,
|
||||
autofocus: true,
|
||||
cursorColor: _ServerPalette.primary,
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.text,
|
||||
fontSize: 14,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
color: _ServerPalette.border,
|
||||
),
|
||||
decoration: _searchFieldDecoration(
|
||||
hintText: l.tr('servers.searchHint'),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
color: _ServerPalette.primary,
|
||||
),
|
||||
onChanged: (value) => setState(
|
||||
() => _query = value.trim().toLowerCase(),
|
||||
),
|
||||
),
|
||||
onChanged: (value) => setState(
|
||||
() => _query = value.trim().toLowerCase(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
@@ -493,10 +462,7 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ServerModel> _filterByGroup(
|
||||
List<ServerModel> servers,
|
||||
String? groupId,
|
||||
) {
|
||||
List<ServerModel> _filterByGroup(List<ServerModel> servers, String? groupId) {
|
||||
if (groupId == null) return servers;
|
||||
return servers
|
||||
.where((server) => _normalizedGroupId(server.group) == groupId)
|
||||
@@ -507,10 +473,7 @@ class _ServersTabState extends ConsumerState<ServersTab> {
|
||||
return groupId.isEmpty ? 'default' : groupId;
|
||||
}
|
||||
|
||||
String _groupDisplayName(
|
||||
ServerModel server,
|
||||
List<ServerGroupModel> groups,
|
||||
) {
|
||||
String _groupDisplayName(ServerModel server, List<ServerGroupModel> groups) {
|
||||
final groupId = _normalizedGroupId(server.group);
|
||||
for (final group in groups) {
|
||||
if (group.id == groupId) return group.displayName;
|
||||
@@ -687,9 +650,9 @@ class _ActiveTerminalBanner extends StatelessWidget {
|
||||
: l.trf('servers.activeTerminalsMany', [count]),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: _ServerPalette.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
color: _ServerPalette.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Icon(
|
||||
@@ -763,156 +726,155 @@ class _ServerCard extends StatelessWidget {
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(14, 14, 14, expanded ? 16 : 14),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
_ServerOsIcon(enabled: server.canConnect),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => state._openForm(server: server),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
server.displayName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.text,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
padding: EdgeInsets.fromLTRB(14, 14, 14, expanded ? 16 : 14),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
_ServerOsIcon(enabled: server.canConnect),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => state._openForm(server: server),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
server.displayName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.text,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
server.connectionLabel,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.softMuted,
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
server.connectionLabel,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: _ServerPalette.softMuted,
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_IconPillButton(
|
||||
tooltip: expanded ? 'Collapse' : 'Expand',
|
||||
icon: expanded
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down,
|
||||
highlighted: expanded,
|
||||
onTap: () {
|
||||
state.setState(() {
|
||||
if (expanded) {
|
||||
state._expandedServerIds.remove(server.id);
|
||||
} else {
|
||||
state._expandedServerIds.add(server.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
if (groupName.isNotEmpty)
|
||||
_InfoChip(label: groupName),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_ConnectButton(
|
||||
connecting: connecting,
|
||||
label: state._actionText(server),
|
||||
enabled: server.canConnect || server.isWindows,
|
||||
onPressed: () => state._connect(server),
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedCrossFade(
|
||||
firstChild: const SizedBox(width: double.infinity),
|
||||
secondChild: Column(
|
||||
children: [
|
||||
const SizedBox(height: 14),
|
||||
const Divider(height: 1, color: _ServerPalette.border),
|
||||
const SizedBox(height: 12),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.tag_outlined,
|
||||
label: l.tr('servers.field.index'),
|
||||
value: '#${server.index}',
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.folder_outlined,
|
||||
label: l.tr('servers.field.group'),
|
||||
value: groupName.isEmpty ? '-' : groupName,
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.sell_outlined,
|
||||
label: l.tr('servers.field.tags'),
|
||||
value: server.tag.isEmpty ? '-' : server.tag.join(', '),
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.key_outlined,
|
||||
label: l.tr('servers.field.authType'),
|
||||
value: state._authTypeLabel(server),
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.hub_outlined,
|
||||
label: l.tr('servers.field.proxyType'),
|
||||
value: proxyLabel,
|
||||
emphasized:
|
||||
server.proxyType != 'none' && server.proxyType.isNotEmpty,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _SecondaryActionButton(
|
||||
icon: Icons.delete_outline,
|
||||
label: l.tr('common.delete'),
|
||||
destructive: true,
|
||||
onTap: () => state._confirmDelete(server),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _SecondaryActionButton(
|
||||
icon: Icons.edit_outlined,
|
||||
label: l.tr('servers.editServer'),
|
||||
onTap: () => state._openForm(server: server),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
_IconPillButton(
|
||||
tooltip: expanded ? 'Collapse' : 'Expand',
|
||||
icon: expanded
|
||||
? Icons.keyboard_arrow_up
|
||||
: Icons.keyboard_arrow_down,
|
||||
highlighted: expanded,
|
||||
onTap: () {
|
||||
state.setState(() {
|
||||
if (expanded) {
|
||||
state._expandedServerIds.remove(server.id);
|
||||
} else {
|
||||
state._expandedServerIds.add(server.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
crossFadeState:
|
||||
expanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 180),
|
||||
sizeCurve: Curves.easeOut,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
if (groupName.isNotEmpty) _InfoChip(label: groupName),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_ConnectButton(
|
||||
connecting: connecting,
|
||||
label: state._actionText(server),
|
||||
enabled: server.canConnect || server.isWindows,
|
||||
onPressed: () => state._connect(server),
|
||||
),
|
||||
],
|
||||
),
|
||||
AnimatedCrossFade(
|
||||
firstChild: const SizedBox(width: double.infinity),
|
||||
secondChild: Column(
|
||||
children: [
|
||||
const SizedBox(height: 14),
|
||||
const Divider(height: 1, color: _ServerPalette.border),
|
||||
const SizedBox(height: 12),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.tag_outlined,
|
||||
label: l.tr('servers.field.index'),
|
||||
value: '#${server.index}',
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.folder_outlined,
|
||||
label: l.tr('servers.field.group'),
|
||||
value: groupName.isEmpty ? '-' : groupName,
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.sell_outlined,
|
||||
label: l.tr('servers.field.tags'),
|
||||
value: server.tag.isEmpty ? '-' : server.tag.join(', '),
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.key_outlined,
|
||||
label: l.tr('servers.field.authType'),
|
||||
value: state._authTypeLabel(server),
|
||||
),
|
||||
_ServerDetailRow(
|
||||
icon: Icons.hub_outlined,
|
||||
label: l.tr('servers.field.proxyType'),
|
||||
value: proxyLabel,
|
||||
emphasized:
|
||||
server.proxyType != 'none' &&
|
||||
server.proxyType.isNotEmpty,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _SecondaryActionButton(
|
||||
icon: Icons.delete_outline,
|
||||
label: l.tr('common.delete'),
|
||||
destructive: true,
|
||||
onTap: () => state._confirmDelete(server),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _SecondaryActionButton(
|
||||
icon: Icons.edit_outlined,
|
||||
label: l.tr('servers.editServer'),
|
||||
onTap: () => state._openForm(server: server),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
crossFadeState: expanded
|
||||
? CrossFadeState.showSecond
|
||||
: CrossFadeState.showFirst,
|
||||
duration: const Duration(milliseconds: 180),
|
||||
sizeCurve: Curves.easeOut,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -976,7 +938,9 @@ class _IconPillButton extends StatelessWidget {
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 20,
|
||||
color: highlighted ? _ServerPalette.primary : _ServerPalette.softMuted,
|
||||
color: highlighted
|
||||
? _ServerPalette.primary
|
||||
: _ServerPalette.softMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1054,28 +1018,34 @@ class _ConnectButton extends StatelessWidget {
|
||||
height: 34,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
enabled ? Icons.play_arrow_rounded : Icons.lock_outline,
|
||||
size: 17,
|
||||
color: enabled ? _ServerPalette.card : _ServerPalette.muted,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: enabled ? _ServerPalette.card : _ServerPalette.muted,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
enabled
|
||||
? Icons.play_arrow_rounded
|
||||
: Icons.lock_outline,
|
||||
size: 17,
|
||||
color: enabled
|
||||
? _ServerPalette.card
|
||||
: _ServerPalette.muted,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: enabled
|
||||
? _ServerPalette.card
|
||||
: _ServerPalette.muted,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1154,8 +1124,9 @@ class _ServerDetailRow extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
color:
|
||||
emphasized ? _ServerPalette.primary : _ServerPalette.text,
|
||||
color: emphasized
|
||||
? _ServerPalette.primary
|
||||
: _ServerPalette.text,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -1182,11 +1153,15 @@ class _SecondaryActionButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final foreground =
|
||||
destructive ? _ServerPalette.danger : _ServerPalette.muted;
|
||||
final background =
|
||||
destructive ? _ServerPalette.dangerSoft : _ServerPalette.chip;
|
||||
final border = destructive ? _ServerPalette.dangerBorder : _ServerPalette.border;
|
||||
final foreground = destructive
|
||||
? _ServerPalette.danger
|
||||
: _ServerPalette.muted;
|
||||
final background = destructive
|
||||
? _ServerPalette.dangerSoft
|
||||
: _ServerPalette.chip;
|
||||
final border = destructive
|
||||
? _ServerPalette.dangerBorder
|
||||
: _ServerPalette.border;
|
||||
return Material(
|
||||
color: background,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -1241,12 +1216,35 @@ class _MessageState extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
InputDecoration _searchFieldDecoration({required String hintText}) {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: _ServerPalette.card,
|
||||
prefixIcon: const Icon(Icons.search, size: 18),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
hintStyle: const TextStyle(color: _ServerPalette.softMuted, fontSize: 13),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: _ServerPalette.border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: _ServerPalette.primary, width: 1.2),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: _ServerPalette.border),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
abstract final class _ServerPalette {
|
||||
static const canvas = Color(0xFFF7EFE0);
|
||||
static const card = Color(0xFFFBF5E6);
|
||||
static const chip = Color(0xFFF4ECD7);
|
||||
static const banner = Color(0xFFF7E4B0);
|
||||
static const accent = Color(0xFFE5B33A);
|
||||
static const primary = Color(0xFF5C4520);
|
||||
static const text = Color(0xFF2A2418);
|
||||
static const muted = Color(0xFF6B5E3F);
|
||||
|
||||
Reference in New Issue
Block a user