Refine cross-platform workspace theme
This commit is contained in:
parent
5d0240def0
commit
545ffd6549
@ -280,6 +280,7 @@ class _AppShellState extends State<AppShell> {
|
||||
palette.chromeBackground,
|
||||
palette.canvas,
|
||||
],
|
||||
stops: const [0.0, 0.68],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
@ -300,8 +301,8 @@ class _AppShellState extends State<AppShell> {
|
||||
alpha:
|
||||
brightness ==
|
||||
Brightness.dark
|
||||
? 0.10
|
||||
: 0.58,
|
||||
? 0.14
|
||||
: 0.42,
|
||||
),
|
||||
palette.chromeHighlight
|
||||
.withValues(alpha: 0),
|
||||
@ -326,8 +327,8 @@ class _AppShellState extends State<AppShell> {
|
||||
alpha:
|
||||
brightness ==
|
||||
Brightness.dark
|
||||
? 0.10
|
||||
: 0.36,
|
||||
? 0.14
|
||||
: 0.24,
|
||||
),
|
||||
palette.chromeInset.withValues(
|
||||
alpha: 0,
|
||||
|
||||
@ -14,6 +14,7 @@ import '../../features/tasks/tasks_page.dart';
|
||||
import '../../i18n/app_language.dart';
|
||||
import '../../models/app_models.dart';
|
||||
import '../../runtime/runtime_models.dart';
|
||||
import '../../theme/app_palette.dart';
|
||||
import '../../widgets/detail_drawer.dart';
|
||||
import '../../widgets/gateway_connect_dialog.dart';
|
||||
|
||||
@ -37,21 +38,10 @@ extension on MobileShellTab {
|
||||
};
|
||||
}
|
||||
|
||||
const _background = Color(0xFFF3EFF6);
|
||||
const _surface = Colors.white;
|
||||
const _surfaceSoft = Color(0xFFF7F4FB);
|
||||
const _stroke = Color(0xFFE3DDEE);
|
||||
const _textPrimary = Color(0xFF101113);
|
||||
const _textSecondary = Color(0xFF8A8694);
|
||||
const _accentStart = Color(0xFF7C88F8);
|
||||
const _accentEnd = Color(0xFF6757EF);
|
||||
const _accentSoft = Color(0xFFD9D5FA);
|
||||
const _blueSoft = Color(0xFFDCE4F1);
|
||||
const _blueLine = Color(0xFF6285A6);
|
||||
const _greenSoft = Color(0xFFDCEFE2);
|
||||
const _greenLine = Color(0xFF62C56A);
|
||||
const _orangeSoft = Color(0xFFF5E7D9);
|
||||
const _orangeLine = Color(0xFFE1913E);
|
||||
const _tealSoft = Color(0xFFDDF3EF);
|
||||
const _tealLine = Color(0xFF49A892);
|
||||
const _violetSoft = Color(0xFFECE2FF);
|
||||
const _violetLine = Color(0xFF7A61B6);
|
||||
|
||||
class MobileShell extends StatefulWidget {
|
||||
const MobileShell({super.key, required this.controller});
|
||||
@ -242,20 +232,32 @@ class _MobileShellState extends State<MobileShell> {
|
||||
'mobile-shell-${widget.controller.destination.name}',
|
||||
);
|
||||
final detailPanel = widget.controller.detailPanel;
|
||||
final palette = context.palette;
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _background,
|
||||
backgroundColor: palette.canvas,
|
||||
body: Stack(
|
||||
children: [
|
||||
const Positioned(
|
||||
Positioned(
|
||||
top: 100,
|
||||
left: -80,
|
||||
child: _GlowOrb(size: 220, color: Color(0x1A8C89FF)),
|
||||
child: _GlowOrb(
|
||||
size: 220,
|
||||
color: palette.accentMuted.withValues(
|
||||
alpha: isDark ? 0.36 : 0.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Positioned(
|
||||
Positioned(
|
||||
right: -90,
|
||||
bottom: 220,
|
||||
child: _GlowOrb(size: 260, color: Color(0x143AB08F)),
|
||||
child: _GlowOrb(
|
||||
size: 260,
|
||||
color: palette.chromeHighlight.withValues(
|
||||
alpha: isDark ? 0.16 : 0.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
@ -264,11 +266,21 @@ class _MobileShellState extends State<MobileShell> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _surface.withValues(alpha: 0.94),
|
||||
border: Border.all(color: _stroke),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(
|
||||
alpha: isDark ? 0.16 : 0.9,
|
||||
),
|
||||
palette.chromeSurface.withValues(alpha: 0.94),
|
||||
],
|
||||
),
|
||||
border: Border.all(color: palette.chromeStroke),
|
||||
boxShadow: [palette.chromeShadowAmbient],
|
||||
),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
@ -298,7 +310,7 @@ class _MobileShellState extends State<MobileShell> {
|
||||
child: GestureDetector(
|
||||
onTap: widget.controller.closeDetail,
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.12),
|
||||
color: Colors.black.withValues(alpha: 0.14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -335,48 +347,49 @@ class _MobileWorkspaceLauncher extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final connection = controller.connection;
|
||||
final palette = context.palette;
|
||||
final entries = <_WorkspaceEntry>[
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.skills,
|
||||
subtitle: appText('技能包与依赖状态', 'Packages and dependency status'),
|
||||
iconColor: _blueLine,
|
||||
iconBackground: _blueSoft,
|
||||
iconColor: palette.accent,
|
||||
iconBackground: palette.accentMuted,
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.nodes,
|
||||
subtitle: appText('边缘节点与实例', 'Edge nodes and instances'),
|
||||
iconColor: const Color(0xFF5CC9B7),
|
||||
iconBackground: const Color(0xFFDDF3EF),
|
||||
iconColor: _tealLine,
|
||||
iconBackground: _tealSoft,
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.agents,
|
||||
subtitle: appText('代理运行态与配置', 'Agent state and configuration'),
|
||||
iconColor: _orangeLine,
|
||||
iconBackground: _orangeSoft,
|
||||
iconColor: palette.warning,
|
||||
iconBackground: palette.warning.withValues(alpha: 0.12),
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.mcpServer,
|
||||
subtitle: appText('MCP 连接与工具注册', 'MCP endpoints and tools'),
|
||||
iconColor: const Color(0xFF5E7CE2),
|
||||
iconBackground: const Color(0xFFE1E8FB),
|
||||
iconColor: palette.accent,
|
||||
iconBackground: palette.accentMuted,
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.clawHub,
|
||||
subtitle: appText('技能与模板市场', 'Marketplace and templates'),
|
||||
iconColor: const Color(0xFF845EC2),
|
||||
iconBackground: const Color(0xFFECE2FF),
|
||||
iconColor: _violetLine,
|
||||
iconBackground: _violetSoft,
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.aiGateway,
|
||||
subtitle: appText('模型与代理网关', 'Models and agent gateway'),
|
||||
iconColor: const Color(0xFF6B5CF2),
|
||||
iconBackground: _accentSoft,
|
||||
iconColor: palette.accent,
|
||||
iconBackground: palette.accentMuted,
|
||||
),
|
||||
_WorkspaceEntry(
|
||||
destination: WorkspaceDestination.account,
|
||||
subtitle: appText('身份、工作区与会话', 'Identity, workspace and sessions'),
|
||||
iconColor: _greenLine,
|
||||
iconBackground: _greenSoft,
|
||||
iconColor: palette.success,
|
||||
iconBackground: palette.success.withValues(alpha: 0.12),
|
||||
),
|
||||
];
|
||||
|
||||
@ -469,21 +482,24 @@ class _LauncherHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final palette = context.palette;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: _textPrimary,
|
||||
style: theme.textTheme.displaySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: palette.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(fontSize: 16, color: _textSecondary),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: palette.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
@ -521,47 +537,54 @@ class _WorkspaceHero extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final palette = context.palette;
|
||||
final statusLabel = connection.status == RuntimeConnectionStatus.connected
|
||||
? appText('会话已就绪', 'Session Ready')
|
||||
: appText('等待接入', 'Awaiting Connection');
|
||||
final statusColor = connection.status == RuntimeConnectionStatus.connected
|
||||
? _greenLine
|
||||
: _textSecondary;
|
||||
? palette.success
|
||||
: palette.textSecondary;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: _surface,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(color: _stroke, width: 1.2),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.86),
|
||||
palette.surfacePrimary.withValues(alpha: 0.94),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: palette.strokeSoft),
|
||||
boxShadow: [palette.chromeShadowAmbient],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
statusLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: statusColor,
|
||||
),
|
||||
style: theme.textTheme.labelLarge?.copyWith(color: statusColor),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
connection.remoteAddress ?? 'xworkmate.svc.plus',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: _textPrimary,
|
||||
style: theme.textTheme.displaySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: palette.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
activeAgentName,
|
||||
style: const TextStyle(fontSize: 16, color: _textSecondary),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: palette.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Wrap(
|
||||
@ -604,23 +627,24 @@ class _HeroMetric extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final palette = context.palette;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _surfaceSoft,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
color: palette.surfaceSecondary.withValues(alpha: 0.94),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: palette.strokeSoft),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 18, color: _blueLine),
|
||||
Icon(icon, size: 18, color: palette.accent),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$label · $value',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _textPrimary,
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: palette.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -637,17 +661,26 @@ class _WorkspaceShortcutCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final palette = context.palette;
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: _surface,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
border: Border.all(color: _stroke, width: 1.2),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.84),
|
||||
palette.surfacePrimary.withValues(alpha: 0.94),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: palette.strokeSoft),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@ -671,10 +704,10 @@ class _WorkspaceShortcutCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
entry.destination.label,
|
||||
style: const TextStyle(
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: _textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: palette.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
@ -682,16 +715,15 @@ class _WorkspaceShortcutCard extends StatelessWidget {
|
||||
entry.subtitle,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: _textSecondary,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: palette.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(Icons.chevron_right_rounded, color: _textSecondary),
|
||||
Icon(Icons.chevron_right_rounded, color: palette.textSecondary),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -708,10 +740,15 @@ class _GradientActionButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final palette = context.palette;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(colors: [_accentStart, _accentEnd]),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [palette.accent, palette.accentHover],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: FilledButton(
|
||||
onPressed: onPressed,
|
||||
@ -719,6 +756,9 @@ class _GradientActionButton extends StatelessWidget {
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
shadowColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
),
|
||||
child: Text(label),
|
||||
@ -735,12 +775,14 @@ class _BottomPillNav extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final palette = context.palette;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xF8FFFFFF),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(color: _stroke),
|
||||
color: palette.surfacePrimary.withValues(alpha: 0.92),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: palette.strokeSoft),
|
||||
boxShadow: [palette.chromeShadowAmbient],
|
||||
),
|
||||
child: Row(
|
||||
children: MobileShellTab.values
|
||||
@ -754,9 +796,9 @@ class _BottomPillNav extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: currentTab == tab
|
||||
? _surfaceSoft
|
||||
? palette.surfaceSecondary
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -764,7 +806,9 @@ class _BottomPillNav extends StatelessWidget {
|
||||
Icon(
|
||||
tab.icon,
|
||||
size: 24,
|
||||
color: currentTab == tab ? _blueLine : _textPrimary,
|
||||
color: currentTab == tab
|
||||
? palette.accent
|
||||
: palette.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
@ -774,7 +818,9 @@ class _BottomPillNav extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: currentTab == tab ? _blueLine : _textPrimary,
|
||||
color: currentTab == tab
|
||||
? palette.accent
|
||||
: palette.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -63,85 +63,85 @@ class AppPalette extends ThemeExtension<AppPalette> {
|
||||
final Color hover;
|
||||
|
||||
static const AppPalette light = AppPalette(
|
||||
canvas: Color(0xFFF5F5F7),
|
||||
sidebar: Color(0xFFF1F1F3),
|
||||
sidebarBorder: Color(0xFFE5E5EA),
|
||||
chromeBackground: Color(0xFFF6F4F1),
|
||||
chromeSurface: Color(0xFFFCFBF9),
|
||||
chromeSurfacePressed: Color(0xFFF2EFEB),
|
||||
canvas: Color(0xFFF8F9FA),
|
||||
sidebar: Color(0xFFF1F4F8),
|
||||
sidebarBorder: Color(0x26A6B4C8),
|
||||
chromeBackground: Color(0xFFF4F7FA),
|
||||
chromeSurface: Color(0xFFFDFEFF),
|
||||
chromeSurfacePressed: Color(0xFFF1F5F9),
|
||||
chromeHighlight: Color(0xFFFFFFFF),
|
||||
chromeStroke: Color(0xFFECE7E0),
|
||||
chromeInset: Color(0xFFF4F1EC),
|
||||
chromeStroke: Color(0x26A6B4C8),
|
||||
chromeInset: Color(0xFFF4F7FA),
|
||||
chromeShadowAmbient: BoxShadow(
|
||||
color: Color(0x12000000),
|
||||
blurRadius: 30,
|
||||
offset: Offset(0, 10),
|
||||
spreadRadius: -10,
|
||||
color: Color(0x140058BD),
|
||||
blurRadius: 40,
|
||||
offset: Offset(0, 12),
|
||||
spreadRadius: -14,
|
||||
),
|
||||
chromeShadowLift: BoxShadow(
|
||||
color: Color(0x14000000),
|
||||
blurRadius: 14,
|
||||
offset: Offset(0, 5),
|
||||
spreadRadius: -6,
|
||||
color: Color(0x180058BD),
|
||||
blurRadius: 24,
|
||||
offset: Offset(0, 10),
|
||||
spreadRadius: -12,
|
||||
),
|
||||
surfacePrimary: Color(0xFFFFFFFF),
|
||||
surfaceSecondary: Color(0xFFF1F1F3),
|
||||
surfaceTertiary: Color(0xFFECECEF),
|
||||
stroke: Color(0xFFE5E5EA),
|
||||
strokeSoft: Color(0xFFECECEF),
|
||||
accent: Color(0xFF4C8BF5),
|
||||
accentHover: Color(0xFF5E98F6),
|
||||
accentMuted: Color(0xFFEEF4FF),
|
||||
idle: Color(0xFFA1A1A6),
|
||||
success: Color(0xFF34C759),
|
||||
warning: Color(0xFFFF9F0A),
|
||||
danger: Color(0xFFFF3B30),
|
||||
textPrimary: Color(0xFF0A0A0A),
|
||||
textSecondary: Color(0xFF6B6B6F),
|
||||
textMuted: Color(0xFFA1A1A6),
|
||||
shadow: Color(0x0A000000),
|
||||
hover: Color(0xFFE5E5EA),
|
||||
surfaceSecondary: Color(0xFFF2F5F8),
|
||||
surfaceTertiary: Color(0xFFE9EEF4),
|
||||
stroke: Color(0x33A6B4C8),
|
||||
strokeSoft: Color(0x26A6B4C8),
|
||||
accent: Color(0xFF0058BD),
|
||||
accentHover: Color(0xFF1A6CCE),
|
||||
accentMuted: Color(0xFFE8F0FB),
|
||||
idle: Color(0xFF98A1B2),
|
||||
success: Color(0xFF34A853),
|
||||
warning: Color(0xFF8F4A00),
|
||||
danger: Color(0xFFC3655C),
|
||||
textPrimary: Color(0xFF1C1B1F),
|
||||
textSecondary: Color(0xFF667085),
|
||||
textMuted: Color(0xFF98A1B2),
|
||||
shadow: Color(0x140058BD),
|
||||
hover: Color(0xFFEFF4FA),
|
||||
);
|
||||
|
||||
static const AppPalette dark = AppPalette(
|
||||
canvas: Color(0xFF0E0F12),
|
||||
sidebar: Color(0xFF15171C),
|
||||
sidebarBorder: Color(0xFF23262D),
|
||||
chromeBackground: Color(0xFF111318),
|
||||
chromeSurface: Color(0xFF1A1D23),
|
||||
chromeSurfacePressed: Color(0xFF16181D),
|
||||
chromeHighlight: Color(0xFF2A2E36),
|
||||
chromeStroke: Color(0xFF2A2F38),
|
||||
chromeInset: Color(0xFF1E2229),
|
||||
canvas: Color(0xFF141422),
|
||||
sidebar: Color(0xFF1A1D2A),
|
||||
sidebarBorder: Color(0x33CAC4D0),
|
||||
chromeBackground: Color(0xFF161A26),
|
||||
chromeSurface: Color(0xFF1D2230),
|
||||
chromeSurfacePressed: Color(0xFF23293A),
|
||||
chromeHighlight: Color(0xFF2A3145),
|
||||
chromeStroke: Color(0x33CAC4D0),
|
||||
chromeInset: Color(0xFF1A1F2C),
|
||||
chromeShadowAmbient: BoxShadow(
|
||||
color: Color(0x52000000),
|
||||
blurRadius: 28,
|
||||
offset: Offset(0, 10),
|
||||
spreadRadius: -10,
|
||||
color: Color(0x4D000814),
|
||||
blurRadius: 36,
|
||||
offset: Offset(0, 12),
|
||||
spreadRadius: -14,
|
||||
),
|
||||
chromeShadowLift: BoxShadow(
|
||||
color: Color(0x68000000),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
spreadRadius: -6,
|
||||
color: Color(0x660058BD),
|
||||
blurRadius: 22,
|
||||
offset: Offset(0, 8),
|
||||
spreadRadius: -12,
|
||||
),
|
||||
surfacePrimary: Color(0xFF15171C),
|
||||
surfaceSecondary: Color(0xFF1B1E24),
|
||||
surfaceTertiary: Color(0xFF22262E),
|
||||
stroke: Color(0xFF2B3038),
|
||||
strokeSoft: Color(0xFF22262E),
|
||||
accent: Color(0xFF4C8BF5),
|
||||
accentHover: Color(0xFF6A9DF7),
|
||||
accentMuted: Color(0xFF1A2740),
|
||||
idle: Color(0xFFA1A1AA),
|
||||
success: Color(0xFF34C759),
|
||||
warning: Color(0xFFFF9F0A),
|
||||
danger: Color(0xFFFF3B30),
|
||||
textPrimary: Color(0xFFFFFFFF),
|
||||
textSecondary: Color(0xFFA1A1AA),
|
||||
textMuted: Color(0xFF737982),
|
||||
surfacePrimary: Color(0xFF171C28),
|
||||
surfaceSecondary: Color(0xFF1E2433),
|
||||
surfaceTertiary: Color(0xFF262D3F),
|
||||
stroke: Color(0x40CAC4D0),
|
||||
strokeSoft: Color(0x26CAC4D0),
|
||||
accent: Color(0xFF4B8FE8),
|
||||
accentHover: Color(0xFF78AFFF),
|
||||
accentMuted: Color(0xFF1C3355),
|
||||
idle: Color(0xFF8B95A8),
|
||||
success: Color(0xFF5CB978),
|
||||
warning: Color(0xFFE0AE5A),
|
||||
danger: Color(0xFFEF9A9A),
|
||||
textPrimary: Color(0xFFE6E1E5),
|
||||
textSecondary: Color(0xFFB0B8C8),
|
||||
textMuted: Color(0xFF8B95A8),
|
||||
shadow: Color(0x52000000),
|
||||
hover: Color(0xFF1B1E24),
|
||||
hover: Color(0xFF23293A),
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@ -21,14 +21,14 @@ class SimpleSpacing {
|
||||
class SimpleRadius {
|
||||
SimpleRadius._();
|
||||
|
||||
static const double card = 6.0;
|
||||
static const double button = 8.0;
|
||||
static const double input = 8.0;
|
||||
static const double chip = 8.0;
|
||||
static const double card = 16.0;
|
||||
static const double button = 12.0;
|
||||
static const double input = 14.0;
|
||||
static const double chip = 12.0;
|
||||
static const double badge = 999.0;
|
||||
static const double dialog = 5.0;
|
||||
static const double sidebar = 8.0;
|
||||
static const double icon = 8.0;
|
||||
static const double dialog = 18.0;
|
||||
static const double sidebar = 20.0;
|
||||
static const double icon = 12.0;
|
||||
}
|
||||
|
||||
class SimpleTypography {
|
||||
@ -66,18 +66,18 @@ class SimpleTypography {
|
||||
class SimpleSizes {
|
||||
SimpleSizes._();
|
||||
|
||||
static const double sidebarItemHeight = 32.0;
|
||||
static const double sidebarItemHeight = 34.0;
|
||||
static const double sidebarIconSize = 18.0;
|
||||
static const double sidebarTextSize = 13.0;
|
||||
static const double sidebarExpandedWidth = 176.0;
|
||||
static const double sidebarCollapsedWidth = 52.0;
|
||||
static const double sidebarExpandedWidth = 188.0;
|
||||
static const double sidebarCollapsedWidth = 56.0;
|
||||
|
||||
static const double textareaHeight = 36.0;
|
||||
static const double toolbarHeight = 40.0;
|
||||
|
||||
static const double inputHeight = 36.0;
|
||||
static const double buttonHeightDesktop = 16.0;
|
||||
static const double buttonHeightMobile = 16.0;
|
||||
static const double inputHeight = 40.0;
|
||||
static const double buttonHeightDesktop = 30.0;
|
||||
static const double buttonHeightMobile = 36.0;
|
||||
}
|
||||
|
||||
class AppSpacing {
|
||||
@ -244,37 +244,45 @@ class AppTheme {
|
||||
selectedColor: palette.surfacePrimary,
|
||||
secondarySelectedColor: palette.surfacePrimary,
|
||||
disabledColor: palette.surfaceSecondary,
|
||||
side: BorderSide.none,
|
||||
side: BorderSide(color: palette.strokeSoft),
|
||||
checkmarkColor: Colors.transparent,
|
||||
labelStyle: tunedTextTheme.labelMedium,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.chip),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: palette.accent,
|
||||
foregroundColor: Colors.white,
|
||||
shadowColor: palette.shadow,
|
||||
elevation: 0,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
textStyle: tunedTextTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
style: ButtonStyle(
|
||||
foregroundColor: const WidgetStatePropertyAll(Colors.white),
|
||||
shadowColor: WidgetStatePropertyAll(palette.shadow),
|
||||
elevation: const WidgetStatePropertyAll(0),
|
||||
surfaceTintColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||
textStyle: WidgetStatePropertyAll(
|
||||
tunedTextTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
minimumSize: Size(
|
||||
0,
|
||||
isDesktop
|
||||
? AppSizes.buttonHeightDesktop
|
||||
: AppSizes.buttonHeightMobile,
|
||||
minimumSize: WidgetStatePropertyAll(
|
||||
Size(
|
||||
0,
|
||||
isDesktop
|
||||
? AppSizes.buttonHeightDesktop
|
||||
: AppSizes.buttonHeightMobile,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm,
|
||||
vertical: 0,
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return palette.accentHover;
|
||||
}
|
||||
return palette.accent;
|
||||
}),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
@ -293,10 +301,7 @@ class AppTheme {
|
||||
? AppSizes.buttonHeightDesktop
|
||||
: AppSizes.buttonHeightMobile,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.sm,
|
||||
vertical: 0,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
@ -316,7 +321,7 @@ class AppTheme {
|
||||
: AppSizes.buttonHeightMobile,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.xs,
|
||||
horizontal: AppSpacing.sm,
|
||||
vertical: 0,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -327,10 +332,10 @@ class AppTheme {
|
||||
iconButtonTheme: IconButtonThemeData(
|
||||
style: IconButton.styleFrom(
|
||||
foregroundColor: palette.textSecondary,
|
||||
backgroundColor: palette.surfaceSecondary,
|
||||
backgroundColor: palette.surfaceSecondary.withValues(alpha: 0.88),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
minimumSize: const Size(40, 40),
|
||||
padding: const EdgeInsets.all(10),
|
||||
minimumSize: const Size(32, 32),
|
||||
padding: const EdgeInsets.all(7),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.icon),
|
||||
),
|
||||
@ -339,7 +344,7 @@ class AppTheme {
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
isDense: true,
|
||||
filled: true,
|
||||
fillColor: palette.surfacePrimary,
|
||||
fillColor: palette.surfacePrimary.withValues(alpha: 0.92),
|
||||
hintStyle: tunedTextTheme.bodyMedium?.copyWith(
|
||||
color: palette.textMuted,
|
||||
),
|
||||
@ -364,7 +369,7 @@ class AppTheme {
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.input),
|
||||
borderSide: BorderSide(color: palette.accent.withValues(alpha: 0.18)),
|
||||
borderSide: BorderSide(color: palette.accent.withValues(alpha: 0.32)),
|
||||
),
|
||||
),
|
||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||
@ -372,7 +377,7 @@ class AppTheme {
|
||||
side: const WidgetStatePropertyAll(BorderSide.none),
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return palette.surfacePrimary;
|
||||
return palette.surfacePrimary.withValues(alpha: 0.96);
|
||||
}
|
||||
return palette.surfaceSecondary;
|
||||
}),
|
||||
@ -400,14 +405,14 @@ class AppTheme {
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: palette.surfacePrimary,
|
||||
backgroundColor: palette.surfacePrimary.withValues(alpha: 0.96),
|
||||
contentTextStyle: TextStyle(color: palette.textPrimary),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.dialog),
|
||||
),
|
||||
),
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
color: palette.surfacePrimary,
|
||||
color: palette.surfacePrimary.withValues(alpha: 0.98),
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -449,7 +454,7 @@ class AppTheme {
|
||||
base.titleLarge?.copyWith(
|
||||
fontSize: AppTypography.sectionSize,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0,
|
||||
letterSpacing: 0.02,
|
||||
height: AppTypography.sectionHeight,
|
||||
color: palette.textPrimary,
|
||||
),
|
||||
@ -506,7 +511,8 @@ class AppTheme {
|
||||
labelMedium: withUiFont(
|
||||
base.labelMedium?.copyWith(
|
||||
fontSize: AppTypography.captionSize,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.2,
|
||||
height: AppTypography.captionHeight,
|
||||
color: palette.textSecondary,
|
||||
),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme/app_palette.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
|
||||
class DesktopWorkspaceScaffold extends StatelessWidget {
|
||||
const DesktopWorkspaceScaffold({
|
||||
@ -48,7 +49,7 @@ class DesktopWorkspaceScaffold extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.labelMedium
|
||||
?.copyWith(
|
||||
color: palette.textMuted,
|
||||
letterSpacing: 0.2,
|
||||
letterSpacing: 0.32,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
@ -101,16 +102,16 @@ class DesktopWorkspaceScaffold extends StatelessWidget {
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.96),
|
||||
palette.chromeSurface,
|
||||
palette.chromeHighlight.withValues(alpha: 0.9),
|
||||
palette.chromeSurface.withValues(alpha: 0.92),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
border: Border.all(color: palette.chromeStroke),
|
||||
boxShadow: [palette.chromeShadowAmbient],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
||||
@ -85,8 +85,8 @@ class SidebarNavigation extends StatelessWidget {
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.96),
|
||||
palette.chromeSurface,
|
||||
palette.chromeHighlight.withValues(alpha: 0.9),
|
||||
palette.chromeSurface.withValues(alpha: 0.92),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppRadius.sidebar),
|
||||
@ -196,8 +196,8 @@ class SidebarHeader extends StatelessWidget {
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.94),
|
||||
palette.chromeSurfacePressed,
|
||||
palette.chromeHighlight.withValues(alpha: 0.88),
|
||||
palette.chromeSurfacePressed.withValues(alpha: 0.92),
|
||||
],
|
||||
),
|
||||
border: Border.all(color: palette.chromeStroke),
|
||||
@ -261,6 +261,7 @@ class _SidebarSectionGroup extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: palette.textMuted,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.28,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -326,7 +327,7 @@ class _SidebarNavItemState extends State<_SidebarNavItem> {
|
||||
final theme = Theme.of(context);
|
||||
final isPrimary = widget.emphasis == _SidebarItemEmphasis.primary;
|
||||
final background = widget.selected
|
||||
? palette.chromeSurface
|
||||
? palette.surfacePrimary
|
||||
: _hovered
|
||||
? palette.chromeSurfacePressed
|
||||
: Colors.transparent;
|
||||
@ -350,9 +351,11 @@ class _SidebarNavItemState extends State<_SidebarNavItem> {
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(
|
||||
alpha: widget.selected ? 0.96 : 0.82,
|
||||
alpha: widget.selected ? 0.84 : 0.7,
|
||||
),
|
||||
background.withValues(
|
||||
alpha: widget.selected ? 0.96 : 0.9,
|
||||
),
|
||||
background,
|
||||
],
|
||||
)
|
||||
: null,
|
||||
@ -372,7 +375,7 @@ class _SidebarNavItemState extends State<_SidebarNavItem> {
|
||||
onTap: widget.onTap,
|
||||
child: Container(
|
||||
height: height,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: widget.collapsed
|
||||
? Center(
|
||||
child: Icon(
|
||||
@ -411,6 +414,7 @@ class _SidebarNavItemState extends State<_SidebarNavItem> {
|
||||
fontWeight: isPrimary
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
letterSpacing: isPrimary ? 0.02 : 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -47,17 +47,22 @@ class _SurfaceCardState extends State<SurfaceCard> {
|
||||
};
|
||||
final decoration = switch (widget.tone) {
|
||||
SurfaceCardTone.standard => BoxDecoration(
|
||||
color: _hovered && widget.onTap != null ? hoveredColor : baseColor,
|
||||
color: (_hovered && widget.onTap != null ? hoveredColor : baseColor)
|
||||
.withValues(alpha: 0.94),
|
||||
border: Border.all(color: borderColor),
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
boxShadow: widget.onTap != null && _hovered
|
||||
? [palette.chromeShadowLift]
|
||||
: const [],
|
||||
),
|
||||
SurfaceCardTone.chrome => BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
palette.chromeHighlight.withValues(alpha: 0.94),
|
||||
_hovered && widget.onTap != null ? hoveredColor : baseColor,
|
||||
palette.chromeHighlight.withValues(alpha: 0.92),
|
||||
(_hovered && widget.onTap != null ? hoveredColor : baseColor)
|
||||
.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
border: Border.all(color: borderColor),
|
||||
@ -74,7 +79,7 @@ class _SurfaceCardState extends State<SurfaceCard> {
|
||||
onExit: (_) => setState(() => _hovered = false),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
curve: Curves.easeInOut,
|
||||
curve: Curves.easeOutCubic,
|
||||
decoration: decoration,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
|
||||
@ -71,11 +71,7 @@ class TopBar extends StatelessWidget {
|
||||
}
|
||||
|
||||
class AppBreadcrumbItem {
|
||||
const AppBreadcrumbItem({
|
||||
required this.label,
|
||||
this.onTap,
|
||||
this.icon,
|
||||
});
|
||||
const AppBreadcrumbItem({required this.label, this.onTap, this.icon});
|
||||
|
||||
final String label;
|
||||
final VoidCallback? onTap;
|
||||
@ -149,10 +145,12 @@ class _BreadcrumbChip extends StatelessWidget {
|
||||
final body = Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: item.onTap != null
|
||||
? palette.surfaceSecondary
|
||||
: palette.surfacePrimary,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
color:
|
||||
(item.onTap != null
|
||||
? palette.surfaceSecondary
|
||||
: palette.surfacePrimary)
|
||||
.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: palette.strokeSoft),
|
||||
),
|
||||
child: content,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user