Files
feature-tenant/lib/core/theme/AppTheme.dart
2026-08-18 08:52:33 +08:00

319 lines
10 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
/// 应用级语义色扩展(浅色/深色各一套,随主题自动切换)
///
/// 用法:`context.appColors.cardBackground`
/// 原理:颜色挂载在 ThemeData.extensions 上,主题切换时组件树自动重建,
/// 页面无需写 `isDark ? A : B` 判断
@immutable
class AppColors extends ThemeExtension<AppColors> {
const AppColors({
required this.pageBackground,
required this.cardBackground,
required this.fillBackground,
required this.textPrimary,
required this.textSecondary,
required this.textTertiary,
required this.divider,
required this.cardShadow,
required this.primary,
required this.danger,
required this.warning,
required this.success,
});
/// 页面背景色
final Color pageBackground;
/// 卡片背景色
final Color cardBackground;
/// 卡片内嵌块填充色(输入框、上传按钮等)
final Color fillBackground;
/// 主要文字色
final Color textPrimary;
/// 次要文字色
final Color textSecondary;
/// 辅助文字色(占位、说明)
final Color textTertiary;
/// 分割线/浅色边框
final Color divider;
/// 卡片阴影色
final Color cardShadow;
/// 品牌主色(两种模式下保持一致)
final Color primary;
/// 危险/错误色(两种模式下保持一致)
final Color danger;
/// 警告色(两种模式下保持一致)
final Color warning;
/// 成功/完成色(两种模式下保持一致)
final Color success;
/// 浅色语义色(取自设计稿)
static const AppColors light = AppColors(
pageBackground: Color(0xFFF5F7FA),
cardBackground: Color(0xFFFFFFFF),
fillBackground: Color(0xFFF8F9FA),
textPrimary: Color(0xFF1D2129),
textSecondary: Color(0xFF4E5969),
textTertiary: Color(0xFF86909C),
divider: Color(0xFFF0F0F0),
cardShadow: Color(0x0D000000),
primary: Color(0xFF165DFF),
danger: Color(0xFFF53F3F),
warning: Color(0xFFFF7D00),
success: Color(0xFF00B42A),
);
/// 深色语义色(与 AppTheme.darkTheme 的 surface 色保持一致)
static const AppColors dark = AppColors(
pageBackground: Color(0xFF1C1C1E),
cardBackground: Color(0xFF2C2C2E),
fillBackground: Color(0xFF3A3A3C),
textPrimary: Color(0xFFF2F3F5),
textSecondary: Color(0xFFC9CDD4),
textTertiary: Color(0xFF86909C),
divider: Color(0xFF3A3A3C),
cardShadow: Color(0x00000000), // 深色下无阴影,靠色差分层级
primary: Color(0xFF165DFF),
danger: Color(0xFFF53F3F),
warning: Color(0xFFFF7D00),
success: Color(0xFF00B42A),
);
@override
AppColors copyWith({
Color? pageBackground,
Color? cardBackground,
Color? fillBackground,
Color? textPrimary,
Color? textSecondary,
Color? textTertiary,
Color? divider,
Color? cardShadow,
Color? primary,
Color? danger,
Color? warning,
Color? success,
}) {
return AppColors(
pageBackground: pageBackground ?? this.pageBackground,
cardBackground: cardBackground ?? this.cardBackground,
fillBackground: fillBackground ?? this.fillBackground,
textPrimary: textPrimary ?? this.textPrimary,
textSecondary: textSecondary ?? this.textSecondary,
textTertiary: textTertiary ?? this.textTertiary,
divider: divider ?? this.divider,
cardShadow: cardShadow ?? this.cardShadow,
primary: primary ?? this.primary,
danger: danger ?? this.danger,
warning: warning ?? this.warning,
success: success ?? this.success,
);
}
@override
AppColors lerp(AppColors? other, double t) {
if (other == null) return this;
return AppColors(
pageBackground: Color.lerp(pageBackground, other.pageBackground, t)!,
cardBackground: Color.lerp(cardBackground, other.cardBackground, t)!,
fillBackground: Color.lerp(fillBackground, other.fillBackground, t)!,
textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
textTertiary: Color.lerp(textTertiary, other.textTertiary, t)!,
divider: Color.lerp(divider, other.divider, t)!,
cardShadow: Color.lerp(cardShadow, other.cardShadow, t)!,
primary: Color.lerp(primary, other.primary, t)!,
danger: Color.lerp(danger, other.danger, t)!,
warning: Color.lerp(warning, other.warning, t)!,
success: Color.lerp(success, other.success, t)!,
);
}
}
/// 便捷访问扩展:`context.appColors`
extension AppColorsX on BuildContext {
AppColors get appColors => Theme.of(this).extension<AppColors>()!;
}
class AppTheme {
// 基础色值定义
static const Color _pureWhite = Color(0xFFFFFFFF);
static const Color _offWhite = Color(0xFFfafafc); // 稍微带点灰的白,用于背景增加层次感
static const Color _pureBlack = Color(0xFF000000);
static const Color _darkGrey = Color(0xFF1C1C1E); // iOS 风格的深灰黑色
static const Color _surfaceDark = Color(0xFF2C2C2E); // 深色模式下卡片、弹窗表面色
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
fontFamily: (Platform.isWindows) ? 'Microsoft YaHei' : null,
// 语义色扩展
extensions: const [AppColors.light],
// 1. 整体背景色
scaffoldBackgroundColor: _offWhite,
// 2. 核心颜色方案
colorScheme: const ColorScheme.light(
primary: _pureBlack, // 主色设为黑色
onPrimary: _pureWhite, // 黑色背景上的文字设为白色
surface: _pureWhite, // 卡片、弹窗等表面颜色
onSurface: _pureBlack, // 表面上的文字色
background: _offWhite,
),
// 3. 全局按钮主题
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: _pureBlack,
foregroundColor: _pureWhite,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
),
),
// 4. 填充按钮主题 (常用语 Material 3)
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: _pureBlack,
foregroundColor: _pureWhite,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// 5. AppBar 主题 (纯白背景,黑色文字)
appBarTheme: const AppBarTheme(
backgroundColor: _pureWhite,
surfaceTintColor: Colors.transparent,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: _pureBlack),
titleTextStyle: TextStyle(
color: _pureBlack,
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
),
),
// 6. 输入框主题 (黑白简约)
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: _pureWhite,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.black12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.black12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _pureBlack, width: 1.5),
),
),
);
}
/// 深色主题 (与浅色主题对称的黑白简约风格)
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
fontFamily: (Platform.isWindows) ? 'Microsoft YaHei' : null,
// 语义色扩展
extensions: const [AppColors.dark],
// 1. 整体背景色 (iOS 风格深灰黑)
scaffoldBackgroundColor: _darkGrey,
// 2. 核心颜色方案 (与浅色主题反色:主色白色)
colorScheme: const ColorScheme.dark(
primary: _pureWhite, // 主色设为白色
onPrimary: _pureBlack, // 白色背景上的文字设为黑色
surface: _surfaceDark, // 卡片、弹窗等表面颜色
onSurface: _pureWhite, // 表面上的文字色
),
// 3. 全局按钮主题 (白底黑字)
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: _pureWhite,
foregroundColor: _pureBlack,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
),
),
// 4. 填充按钮主题 (常用语 Material 3)
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: _pureWhite,
foregroundColor: _pureBlack,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
// 5. AppBar 主题 (深灰黑背景,白色文字)
appBarTheme: const AppBarTheme(
backgroundColor: _darkGrey,
surfaceTintColor: Colors.transparent,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: _pureWhite),
titleTextStyle: TextStyle(
color: _pureWhite,
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
),
),
// 6. 输入框主题 (深色填充,白色边框)
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: _surfaceDark,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.white12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.white12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _pureWhite, width: 1.5),
),
),
);
}
}