feat(flutter): 应用浅色暖橙视觉主题

This commit is contained in:
Zhou Weidong committed 2026-10-20 10:15:03 +08:00
1 parent 0d2367431d
commit 634b63e16c
56 files changed
+941 -409

No files matched your search

+79 -86
View File
@@ -6,79 +6,62 @@ import 'package:flutter/services.dart';
import 'app_colors.dart';
import 'app_dimens.dart';
/// Obsidian Flare themes. Dark is the product default; light mirrors the same
/// orange primary for system-light devices.
/// K28-S 浅色暖橙主题。
abstract final class AppTheme {
static const _lightBackground = Color(0xFFF7F7F8);
static const _darkBackground = AppColors.ink;
/// Light theme for system light mode.
static ThemeData light() => _build(
brightness: Brightness.light,
surface: _lightBackground,
accent: AppColors.blue,
/// 页面顶部暖色到米白背景的渐变。
static const LinearGradient pageGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.surfaceTop, AppColors.ink],
stops: [0, 0.24],
);
/// Dark theme for system dark mode.
static ThemeData dark() => _build(
brightness: Brightness.dark,
surface: _darkBackground,
accent: AppColors.blue,
/// 卡片轻阴影。
static const BoxShadow cardShadow = BoxShadow(
color: AppColors.cardShadow,
blurRadius: AppDimens.cardShadowBlur,
offset: Offset(0, AppDimens.cardShadowY),
);
static ThemeData _build({
required Brightness brightness,
required Color surface,
required Color accent,
}) {
final dark = brightness == Brightness.dark;
final scheme = dark
? const ColorScheme.dark(
primary: AppColors.blue,
onPrimary: AppColors.onPrimary,
secondary: AppColors.secondary,
onSecondary: AppColors.onPrimary,
primaryContainer: AppColors.bluePressed,
onPrimaryContainer: AppColors.onPrimary,
secondaryContainer: AppColors.raised,
onSecondaryContainer: AppColors.onPrimary,
surface: _darkBackground,
onSurface: AppColors.onSurface,
outline: AppColors.outlineDark,
outlineVariant: AppColors.outlineVariantDark,
surfaceTint: AppColors.blue,
inversePrimary: AppColors.blueSoft,
)
: ColorScheme.light(
primary: AppColors.blue,
onPrimary: AppColors.onPrimary,
secondary: AppColors.secondary,
onSecondary: AppColors.onPrimary,
primaryContainer: AppColors.blue.withValues(alpha: 0.14),
onPrimaryContainer: AppColors.ink,
secondaryContainer: AppColors.blueSoft.withValues(alpha: 0.14),
onSecondaryContainer: AppColors.ink,
surface: _lightBackground,
onSurface: AppColors.ink,
outline: AppColors.outlineLight,
outlineVariant: AppColors.outlineVariantLight,
surfaceTint: AppColors.blue,
inversePrimary: AppColors.blueSoft,
);
/// 全局浅色暖橙主题。
static ThemeData light() {
const scheme = ColorScheme.light(
primary: AppColors.blue,
onPrimary: AppColors.onPrimary,
secondary: AppColors.secondary,
onSecondary: AppColors.onPrimary,
primaryContainer: AppColors.surfaceTop,
onPrimaryContainer: AppColors.onSurface,
secondaryContainer: AppColors.badgePink,
onSecondaryContainer: AppColors.badgePinkText,
surface: AppColors.ink,
onSurface: AppColors.onSurface,
outline: AppColors.border,
outlineVariant: AppColors.border,
surfaceTint: AppColors.blue,
inversePrimary: AppColors.bluePressed,
);
final base = ThemeData(
useMaterial3: true,
brightness: brightness,
brightness: Brightness.light,
colorScheme: scheme,
scaffoldBackgroundColor: surface,
scaffoldBackgroundColor: AppColors.ink,
fontFamily: 'PlusJakartaSans',
progressIndicatorTheme: ProgressIndicatorThemeData(color: accent),
sliderTheme: SliderThemeData(
activeTrackColor: accent,
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColors.blue,
),
sliderTheme: const SliderThemeData(
activeTrackColor: AppColors.blue,
inactiveTrackColor: AppColors.border,
thumbColor: AppColors.onPrimary,
),
tabBarTheme: TabBarThemeData(labelColor: accent, indicatorColor: accent),
tabBarTheme: const TabBarThemeData(
labelColor: AppColors.blue,
indicatorColor: AppColors.blue,
unselectedLabelColor: AppColors.muted,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(foregroundColor: accent),
style: TextButton.styleFrom(foregroundColor: AppColors.blue),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
@@ -87,25 +70,26 @@ abstract final class AppTheme {
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
shape: const WidgetStatePropertyAll(StadiumBorder()),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(
dark ? AppColors.blueSoft : AppColors.bluePressed,
),
foregroundColor: const WidgetStatePropertyAll(AppColors.blue),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
side: WidgetStatePropertyAll(
BorderSide(
color: dark ? AppColors.outlineDark : AppColors.outlineLight,
),
side: const WidgetStatePropertyAll(
BorderSide(color: AppColors.border),
),
shape: const WidgetStatePropertyAll(StadiumBorder()),
),
),
appBarTheme: const AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle.light,
backgroundColor: AppColors.ink,
foregroundColor: AppColors.onSurface,
elevation: 0,
systemOverlayStyle: SystemUiOverlayStyle.dark,
titleTextStyle: TextStyle(
color: AppColors.onSurface,
fontFamily: 'Outfit',
@@ -113,6 +97,16 @@ abstract final class AppTheme {
fontWeight: FontWeight.w800,
),
),
iconTheme: const IconThemeData(color: AppColors.onSurface),
dividerTheme: const DividerThemeData(color: AppColors.border),
cardTheme: const CardThemeData(
color: AppColors.card,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(AppDimens.cardRadius)),
side: BorderSide(color: AppColors.border),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
@@ -120,36 +114,35 @@ abstract final class AppTheme {
: null,
),
trackColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected) ? accent : null,
(states) =>
states.contains(WidgetState.selected) ? AppColors.blue : null,
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.bluePressed,
contentTextStyle: const TextStyle(color: AppColors.onPrimary),
snackBarTheme: const SnackBarThemeData(
backgroundColor: AppColors.onSurface,
contentTextStyle: TextStyle(color: AppColors.onPrimary),
),
);
final textTheme = base.textTheme
.apply(
fontFamily: 'PlusJakartaSans',
bodyColor: dark ? AppColors.cream : AppColors.ink,
displayColor: dark ? AppColors.onSurface : AppColors.ink,
bodyColor: AppColors.cream,
displayColor: AppColors.onSurface,
)
.copyWith(
displayLarge: _display(base.textTheme.displayLarge, dark),
displayMedium: _display(base.textTheme.displayMedium, dark),
displaySmall: _display(base.textTheme.displaySmall, dark),
headlineLarge: _display(base.textTheme.headlineLarge, dark),
headlineMedium: _display(base.textTheme.headlineMedium, dark),
headlineSmall: _display(base.textTheme.headlineSmall, dark),
titleLarge: _display(base.textTheme.titleLarge, dark),
displayLarge: _display(base.textTheme.displayLarge),
displayMedium: _display(base.textTheme.displayMedium),
displaySmall: _display(base.textTheme.displaySmall),
headlineLarge: _display(base.textTheme.headlineLarge),
headlineMedium: _display(base.textTheme.headlineMedium),
headlineSmall: _display(base.textTheme.headlineSmall),
titleLarge: _display(base.textTheme.titleLarge),
);
return base.copyWith(textTheme: textTheme);
}
static TextStyle? _display(TextStyle? style, bool dark) => style?.copyWith(
fontFamily: 'Outfit',
color: dark ? AppColors.onSurface : AppColors.ink,
);
static TextStyle? _display(TextStyle? style) =>
style?.copyWith(fontFamily: 'Outfit', color: AppColors.onSurface);
static Color _buttonColor(Set<WidgetState> states) {
if (states.contains(WidgetState.pressed) ||