import 'package:flutter/material.dart'; /// 应用主题:橙白「双橙策略」,回归并与旧版「云端手机端」视觉延续。 /// /// 设计依据:docs/手机端UI优化方案-v2.md /// - 行动色 [brandPrimary] (#C2410C) 承载白字(对比度 5.18 ✅),用于主按钮 / 导航选中 / 可点文字。 /// - 品牌亮橙 [brandOrange] (#FF914D) 仅用于大色块 / 渐变 / 图表,绝不配白字(对比度仅 2.23 ❌)。 /// - 辅金 [accentGold] (#FFBE58) 用于高亮 / 星级 / 推荐位。 class AppTheme { AppTheme._(); /// 行动色(主色)。 static const Color brandPrimary = Color(0xFFC2410C); /// 品牌亮橙(仅大色块 / 渐变 / 图表)。 static const Color brandOrange = Color(0xFFFF914D); /// 辅金。 static const Color accentGold = Color(0xFFFFBE58); static const ColorScheme _light = ColorScheme( brightness: Brightness.light, primary: Color(0xFFC2410C), onPrimary: Color(0xFFFFFFFF), primaryContainer: Color(0xFFFFD3B3), onPrimaryContainer: Color(0xFF4A1D00), secondary: Color(0xFF8A5A3A), onSecondary: Color(0xFFFFFFFF), secondaryContainer: Color(0xFFF5DFCB), onSecondaryContainer: Color(0xFF5A3A22), tertiary: Color(0xFF9A6000), onTertiary: Color(0xFFFFFFFF), tertiaryContainer: Color(0xFFFFE0A3), onTertiaryContainer: Color(0xFF5C3D00), error: Color(0xFFB02A20), onError: Color(0xFFFFFFFF), errorContainer: Color(0xFFFFDDD8), onErrorContainer: Color(0xFF7A2A22), surface: Color(0xFFFCFAF8), onSurface: Color(0xFF1A1614), onSurfaceVariant: Color(0xFF6E655C), surfaceContainerLowest: Color(0xFFFFFFFF), surfaceContainerLow: Color(0xFFF7F3EF), surfaceContainer: Color(0xFFF2EDE8), surfaceContainerHigh: Color(0xFFECE5DF), surfaceContainerHighest: Color(0xFFE5DED7), outline: Color(0xFF8A8178), outlineVariant: Color(0xFFDCCFC2), shadow: Color(0xFF5C2E12), scrim: Color(0xFF1A1614), inverseSurface: Color(0xFF2E2823), onInverseSurface: Color(0xFFF7EFE9), inversePrimary: Color(0xFFFFB487), surfaceTint: Color(0xFFC2410C), ); static const ColorScheme _dark = ColorScheme( brightness: Brightness.dark, primary: Color(0xFFFFB487), onPrimary: Color(0xFF542000), primaryContainer: Color(0xFF63300D), onPrimaryContainer: Color(0xFFFFDCC2), secondary: Color(0xFFE8C39B), onSecondary: Color(0xFF542000), secondaryContainer: Color(0xFF5A3A22), onSecondaryContainer: Color(0xFFFFDCC2), tertiary: Color(0xFFF2BE62), onTertiary: Color(0xFF542000), tertiaryContainer: Color(0xFF5C3D00), onTertiaryContainer: Color(0xFFFFDCC2), error: Color(0xFFFF8474), onError: Color(0xFF5A1610), errorContainer: Color(0xFF7A2A22), onErrorContainer: Color(0xFFFFC9C2), surface: Color(0xFF16130F), onSurface: Color(0xFFF3E9E2), onSurfaceVariant: Color(0xFFC4B8AE), surfaceContainerLowest: Color(0xFF0F0D0A), surfaceContainerLow: Color(0xFF1E1A16), surfaceContainer: Color(0xFF23201B), surfaceContainerHigh: Color(0xFF2D2924), surfaceContainerHighest: Color(0xFF38332D), outline: Color(0xFF7A7068), outlineVariant: Color(0xFF453F39), shadow: Color(0xFF000000), scrim: Color(0xFF0F0D0A), inverseSurface: Color(0xFFF3E9E2), onInverseSurface: Color(0xFF2E2823), inversePrimary: Color(0xFFC2410C), surfaceTint: Color(0xFFFFB487), ); /// 浅色主题(橙白品牌主场)。 static ThemeData light() => _base(_light).copyWith( extensions: const >[AppSemanticColors.light], ); /// 深色主题(暖化 surface,主色提亮为 #FFB487)。 static ThemeData dark() => _base(_dark).copyWith( extensions: const >[AppSemanticColors.dark], ); static ThemeData _base(ColorScheme scheme) { return ThemeData( useMaterial3: true, brightness: scheme.brightness, colorScheme: scheme, scaffoldBackgroundColor: scheme.surface, // 暖棕低透明阴影,替代默认偏硬的黑色阴影(云感)。 shadowColor: const Color(0x245C2E12), materialTapTargetSize: MaterialTapTargetSize.padded, visualDensity: VisualDensity.standard, extensions: const >[], iconTheme: IconThemeData( size: AppIconSize.regular, color: scheme.onSurfaceVariant, ), appBarTheme: AppBarTheme( centerTitle: true, backgroundColor: scheme.surface, foregroundColor: scheme.onSurface, elevation: 0, // 滚动后轻微浮起,呼应云影。 scrolledUnderElevation: 3, shadowColor: const Color(0x245C2E12), ), cardTheme: CardTheme( elevation: 0, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), ), chipTheme: ChipThemeData( backgroundColor: scheme.surfaceContainerHighest, labelStyle: TextStyle(color: scheme.onSurface), selectedColor: scheme.primaryContainer, checkmarkColor: scheme.onPrimaryContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.full), ), ), inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.md), ), filled: true, fillColor: scheme.surfaceContainerHighest, labelStyle: TextStyle(color: scheme.onSurfaceVariant), hintStyle: TextStyle(color: scheme.onSurfaceVariant), errorStyle: TextStyle(color: scheme.error), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), snackBarTheme: const SnackBarThemeData( behavior: SnackBarBehavior.floating, ), // 全局兜底:30 处 showModalBottomSheet 一次性拿到把手 / 安全区 / 顶部圆角 / 限宽。 bottomSheetTheme: BottomSheetThemeData( showDragHandle: true, backgroundColor: scheme.surface, surfaceTintColor: scheme.surfaceTint, modalBackgroundColor: scheme.surface, elevation: 0, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder(borderRadius: AppRadius.sheetTop), constraints: const BoxConstraints(maxWidth: 640), ), navigationBarTheme: NavigationBarThemeData( height: 72, elevation: 0, backgroundColor: scheme.surfaceContainer, surfaceTintColor: scheme.surfaceTint, indicatorColor: scheme.secondaryContainer, indicatorShape: const StadiumBorder(), labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, iconTheme: WidgetStateProperty.resolveWith((states) => IconThemeData( size: 24, color: states.contains(WidgetState.selected) ? scheme.onSecondaryContainer : scheme.onSurfaceVariant, )), labelTextStyle: WidgetStateProperty.resolveWith((states) => TextStyle( fontSize: 12, fontWeight: states.contains(WidgetState.selected) ? FontWeight.w600 : FontWeight.w500, color: states.contains(WidgetState.selected) ? scheme.onSurface : scheme.onSurfaceVariant, )), ), navigationRailTheme: NavigationRailThemeData( backgroundColor: scheme.surfaceContainer, elevation: 0, indicatorColor: scheme.secondaryContainer, indicatorShape: const StadiumBorder(), minWidth: 72, groupAlignment: -0.2, labelType: NavigationRailLabelType.all, selectedIconTheme: IconThemeData(size: 24, color: scheme.onSecondaryContainer), unselectedIconTheme: IconThemeData(size: 24, color: scheme.onSurfaceVariant), selectedLabelTextStyle: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: scheme.onSurface, ), unselectedLabelTextStyle: TextStyle( fontSize: 12, color: scheme.onSurfaceVariant, ), ), dialogTheme: DialogThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.xxl), ), backgroundColor: scheme.surfaceContainerLow, surfaceTintColor: scheme.surfaceTint, ), iconButtonTheme: IconButtonThemeData( style: IconButton.styleFrom( minimumSize: const Size(48, 48), tapTargetSize: MaterialTapTargetSize.padded, padding: EdgeInsets.zero, ), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( minimumSize: const Size(64, 52), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( minimumSize: const Size(64, 52), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( minimumSize: const Size(64, 52), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), ), ), ); } } /// 圆角梯度:非胶囊、四角一致、克制感(呼应 logo ≈3% 圆角)。 class AppRadius { static const double xs = 4; // 进度条、缩略图角 static const double sm = 8; // Chip、SnackBar、小标签 static const double md = 12; // 输入框(沿用现值,保持手感连续) static const double lg = 16; // ★ 标准卡片、按钮(主力值) static const double xl = 20; // 分组容器、大 Banner 卡 static const double xxl = 28; // ★ 底部弹窗顶部两角(云朵弧线核心表达) static const double full = 999; // 仅状态 Chip / 头像 / pill /// 底部弹窗顶部两角大圆角、底部直角(云朵上缘多弧、下缘平缓)。 static const BorderRadius sheetTop = BorderRadius.vertical(top: Radius.circular(xxl)); AppRadius._(); } /// 云感暖阴影:模糊:偏移 ≈4:1,暖棕而非纯黑。 class AppShadows { static const List xs = [ BoxShadow( color: Color(0x0F5C2E12), blurRadius: 8, offset: Offset(0, 2), spreadRadius: -1, ), ]; static const List sm = [ BoxShadow( color: Color(0x145C2E12), blurRadius: 12, offset: Offset(0, 4), spreadRadius: -2, ), ]; static const List md = [ BoxShadow( color: Color(0x1A5C2E12), blurRadius: 20, offset: Offset(0, 8), spreadRadius: -4, ), ]; static const List lg = [ BoxShadow( color: Color(0x245C2E12), blurRadius: 32, offset: Offset(0, 12), spreadRadius: -6, ), ]; static const List xl = [ BoxShadow( color: Color(0x245C2E12), blurRadius: 48, offset: Offset(0, 20), spreadRadius: -8, ), ]; /// 深色:暖色阴影在暗底不可见,改用纯黑 + 描边。 static const List dark = [ BoxShadow( color: Color(0x66000000), blurRadius: 24, offset: Offset(0, 8), spreadRadius: -4, ), ]; AppShadows._(); } /// 图标尺寸栅格(24dp 为默认)。 class AppIconSize { static const double inline = 16; // 正文行内 static const double dense = 20; // 密集信息行 static const double regular = 24; // ★ 默认:列表 leading、按钮内、AppBar actions static const double medium = 28; // 卡片强调图标 static const double large = 32; // 列表主 leading static const double display = 48; // 空状态 / 结果页 static const double hero = 64; // 关于页 logo、引导页 AppIconSize._(); } /// 品牌视觉常量:logo 云朵橙→金渐变。 class AppBrand { static const Color cloud = Color(0xFFFF914D); // logo 主色 static const Color cloudGold = Color(0xFFFFBE58); // logo 金黄 static const Color onCloud = Color(0xFF3D1A00); // 亮橙底上的深棕文字(6.99 ✅) /// logo 橙→金渐变(云朵受光)。 static const LinearGradient cloudGradient = LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFF914D), Color(0xFFFFBE58)], ); AppBrand._(); } /// 语义色(成功 / 警告 / 错误 / 信息),通过 ThemeExtension 暴露。 /// /// 警告用琥珀黄而非橙,避免与品牌橙语义打架。 class AppSemanticColors extends ThemeExtension { const AppSemanticColors({ required this.success, required this.successContainer, required this.onSuccessContainer, required this.warning, required this.warningContainer, required this.onWarningContainer, required this.info, required this.infoContainer, required this.onInfoContainer, }); final Color success; final Color successContainer; final Color onSuccessContainer; final Color warning; final Color warningContainer; final Color onWarningContainer; final Color info; final Color infoContainer; final Color onInfoContainer; static const light = AppSemanticColors( success: Color(0xFF1E7A4E), successContainer: Color(0xFFD3EDDD), onSuccessContainer: Color(0xFF1E5138), warning: Color(0xFF8A5C00), warningContainer: Color(0xFFFFE9B8), onWarningContainer: Color(0xFF5C4100), info: Color(0xFF0B6397), infoContainer: Color(0xFFD7E9FA), onInfoContainer: Color(0xFF1A4763), ); static const dark = AppSemanticColors( success: Color(0xFF5FD398), successContainer: Color(0xFF1E5138), onSuccessContainer: Color(0xFFA8E6C4), warning: Color(0xFFF0BE4E), warningContainer: Color(0xFF5C4100), onWarningContainer: Color(0xFFFFDCA8), info: Color(0xFF7FC4F0), infoContainer: Color(0xFF1A4763), onInfoContainer: Color(0xFFB8DCF5), ); @override ThemeExtension copyWith() => this; @override ThemeExtension lerp( covariant ThemeExtension? other, double t) => other ?? this; }