diff --git a/lib/app/app_shell.dart b/lib/app/app_shell.dart index 58f363d7..ee965a9f 100644 --- a/lib/app/app_shell.dart +++ b/lib/app/app_shell.dart @@ -280,6 +280,7 @@ class _AppShellState extends State { palette.chromeBackground, palette.canvas, ], + stops: const [0.0, 0.68], ), ), child: Stack( @@ -300,8 +301,8 @@ class _AppShellState extends State { 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 { alpha: brightness == Brightness.dark - ? 0.10 - : 0.36, + ? 0.14 + : 0.24, ), palette.chromeInset.withValues( alpha: 0, diff --git a/lib/features/mobile/mobile_shell.dart b/lib/features/mobile/mobile_shell.dart index 16f76837..c49c9443 100644 --- a/lib/features/mobile/mobile_shell.dart +++ b/lib/features/mobile/mobile_shell.dart @@ -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 { '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 { 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 { 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, ), ), ], diff --git a/lib/theme/app_palette.dart b/lib/theme/app_palette.dart index 46be5e11..cae7f18c 100644 --- a/lib/theme/app_palette.dart +++ b/lib/theme/app_palette.dart @@ -63,85 +63,85 @@ class AppPalette extends ThemeExtension { 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 diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 70ef4f8e..38996b54 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -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, ), diff --git a/lib/widgets/desktop_workspace_scaffold.dart b/lib/widgets/desktop_workspace_scaffold.dart index 8ae8193c..0163a935 100644 --- a/lib/widgets/desktop_workspace_scaffold.dart +++ b/lib/widgets/desktop_workspace_scaffold.dart @@ -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, ), ), diff --git a/lib/widgets/sidebar_navigation.dart b/lib/widgets/sidebar_navigation.dart index c5256c99..164bf3c6 100644 --- a/lib/widgets/sidebar_navigation.dart +++ b/lib/widgets/sidebar_navigation.dart @@ -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, ), ), ), diff --git a/lib/widgets/surface_card.dart b/lib/widgets/surface_card.dart index ab01d384..ba5aee6a 100644 --- a/lib/widgets/surface_card.dart +++ b/lib/widgets/surface_card.dart @@ -47,17 +47,22 @@ class _SurfaceCardState extends State { }; 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 { onExit: (_) => setState(() => _hovered = false), child: AnimatedContainer( duration: const Duration(milliseconds: 180), - curve: Curves.easeInOut, + curve: Curves.easeOutCubic, decoration: decoration, child: Material( color: Colors.transparent, diff --git a/lib/widgets/top_bar.dart b/lib/widgets/top_bar.dart index b553d4e5..9046db46 100644 --- a/lib/widgets/top_bar.dart +++ b/lib/widgets/top_bar.dart @@ -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,