feat: 优化Server顶部

This commit is contained in:
chaos-zhu
2026-05-23 17:07:03 +08:00
parent ede37a131c
commit 44b60ec5bf
2 changed files with 274 additions and 645 deletions
+23 -392
View File
@@ -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"
}
]
}
]
}
]
}
+251 -253
View File
@@ -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);