此提交包含多项变更: 1. 新增鸿蒙平台支持,完善设备检测与数据库适配 2. 替换旧版分享插件API为SharePlus 3. 批量迁移StateNotifier到Notifier以适配新版Riverpod 4. 修复zip编码判断、图表API参数等bug 5. 更新应用图标、启动页资源与多尺寸适配图标 6. 调整Android最小SDK版本与应用名称 7. 优化日志打印与正则表达式使用 8. 修正编辑器画布样式初始化与配置逻辑 9. 更新依赖与CI插件配置
780 lines
26 KiB
Dart
780 lines
26 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 主题系统
|
||
/// 创建时间: 2026-04-20
|
||
/// 更新时间: 2026-05-16
|
||
/// 作用: 统一 ThemeData 配置,整合设计令牌,支持日/夜切换
|
||
/// 上次更新: 补充语义色(success/error/warning/info/destructive)默认值改为CupertinoColors系统色
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter/material.dart';
|
||
|
||
import 'app_colors.dart';
|
||
import 'app_radius.dart';
|
||
import 'app_spacing.dart';
|
||
import 'app_typography.dart';
|
||
import 'glass_tokens.dart';
|
||
|
||
// ============================================================
|
||
// ThemeExtension — 自定义扩展令牌
|
||
// ============================================================
|
||
|
||
/// 应用自定义主题扩展
|
||
///
|
||
/// 将设计系统中无法通过标准 ThemeData 配置的属性统一管理,
|
||
/// 如背景色、图标色、Glass 参数、强调色等。
|
||
@immutable
|
||
class AppThemeExtension extends ThemeExtension<AppThemeExtension> {
|
||
const AppThemeExtension({
|
||
required this.bgPrimary,
|
||
required this.bgSecondary,
|
||
required this.bgCard,
|
||
required this.bgElevated,
|
||
required this.textPrimary,
|
||
required this.textSecondary,
|
||
required this.textHint,
|
||
required this.textDisabled,
|
||
required this.textInverse,
|
||
required this.iconPrimary,
|
||
required this.iconSecondary,
|
||
required this.iconDisabled,
|
||
required this.overlaySubtle,
|
||
required this.overlayMedium,
|
||
required this.overlayStrong,
|
||
required this.ripple,
|
||
required this.glassColor,
|
||
required this.glassBorderOpacity,
|
||
required this.isDark,
|
||
this.accent = const Color(0xFF6C63FF),
|
||
this.accentLight = const Color(0xFF8B83FF),
|
||
this.successColor = CupertinoColors.systemGreen,
|
||
this.errorColor = CupertinoColors.systemRed,
|
||
this.warningColor = CupertinoColors.systemOrange,
|
||
this.infoColor = CupertinoColors.systemBlue,
|
||
this.destructiveColor = CupertinoColors.systemRed,
|
||
this.textOnAccent = const Color(0xFFFFFFFF),
|
||
this.isAmoled = false,
|
||
this.glassBlurMultiplier = 1.0,
|
||
this.cardStyleId = 'standard',
|
||
this.cornerRadiusId = 'standard',
|
||
this.fontScale = 1.0,
|
||
this.fontWeight = FontWeight.w400,
|
||
this.fontFamily = 'Inter',
|
||
});
|
||
|
||
// ---- 背景色 ----
|
||
final Color bgPrimary;
|
||
final Color bgSecondary;
|
||
final Color bgCard;
|
||
final Color bgElevated;
|
||
|
||
// ---- 文字色 ----
|
||
final Color textPrimary;
|
||
final Color textSecondary;
|
||
final Color textHint;
|
||
final Color textDisabled;
|
||
final Color textInverse;
|
||
|
||
// ---- 图标色 ----
|
||
final Color iconPrimary;
|
||
final Color iconSecondary;
|
||
final Color iconDisabled;
|
||
|
||
// ---- 遮罩色 ----
|
||
final Color overlaySubtle;
|
||
final Color overlayMedium;
|
||
final Color overlayStrong;
|
||
|
||
// ---- 波纹色 ----
|
||
final Color ripple;
|
||
|
||
// ---- Glass 参数 ----
|
||
final Color glassColor;
|
||
final double glassBorderOpacity;
|
||
|
||
// ---- 模式标识 ----
|
||
final bool isDark;
|
||
|
||
// ---- 强调色 ----
|
||
final Color accent;
|
||
final Color accentLight;
|
||
|
||
// ---- 语义色 ----
|
||
final Color successColor;
|
||
final Color errorColor;
|
||
final Color warningColor;
|
||
final Color infoColor;
|
||
final Color destructiveColor;
|
||
final Color textOnAccent;
|
||
|
||
// ---- AMOLED纯黑 ----
|
||
final bool isAmoled;
|
||
|
||
// ---- 动态字体 ----
|
||
final double fontScale;
|
||
final FontWeight fontWeight;
|
||
final String fontFamily;
|
||
|
||
// ---- 毛玻璃倍率 ----
|
||
final double glassBlurMultiplier;
|
||
|
||
// ---- 卡片样式 ----
|
||
final String cardStyleId;
|
||
|
||
// ---- 圆角风格 ----
|
||
final String cornerRadiusId;
|
||
|
||
@override
|
||
AppThemeExtension copyWith({
|
||
Color? bgPrimary,
|
||
Color? bgSecondary,
|
||
Color? bgCard,
|
||
Color? bgElevated,
|
||
Color? textPrimary,
|
||
Color? textSecondary,
|
||
Color? textHint,
|
||
Color? textDisabled,
|
||
Color? textInverse,
|
||
Color? iconPrimary,
|
||
Color? iconSecondary,
|
||
Color? iconDisabled,
|
||
Color? overlaySubtle,
|
||
Color? overlayMedium,
|
||
Color? overlayStrong,
|
||
Color? ripple,
|
||
Color? glassColor,
|
||
double? glassBorderOpacity,
|
||
bool? isDark,
|
||
Color? accent,
|
||
Color? accentLight,
|
||
Color? successColor,
|
||
Color? errorColor,
|
||
Color? warningColor,
|
||
Color? infoColor,
|
||
Color? destructiveColor,
|
||
Color? textOnAccent,
|
||
bool? isAmoled,
|
||
double? glassBlurMultiplier,
|
||
String? cardStyleId,
|
||
String? cornerRadiusId,
|
||
double? fontScale,
|
||
FontWeight? fontWeight,
|
||
String? fontFamily,
|
||
}) {
|
||
return AppThemeExtension(
|
||
bgPrimary: bgPrimary ?? this.bgPrimary,
|
||
bgSecondary: bgSecondary ?? this.bgSecondary,
|
||
bgCard: bgCard ?? this.bgCard,
|
||
bgElevated: bgElevated ?? this.bgElevated,
|
||
textPrimary: textPrimary ?? this.textPrimary,
|
||
textSecondary: textSecondary ?? this.textSecondary,
|
||
textHint: textHint ?? this.textHint,
|
||
textDisabled: textDisabled ?? this.textDisabled,
|
||
textInverse: textInverse ?? this.textInverse,
|
||
iconPrimary: iconPrimary ?? this.iconPrimary,
|
||
iconSecondary: iconSecondary ?? this.iconSecondary,
|
||
iconDisabled: iconDisabled ?? this.iconDisabled,
|
||
overlaySubtle: overlaySubtle ?? this.overlaySubtle,
|
||
overlayMedium: overlayMedium ?? this.overlayMedium,
|
||
overlayStrong: overlayStrong ?? this.overlayStrong,
|
||
ripple: ripple ?? this.ripple,
|
||
glassColor: glassColor ?? this.glassColor,
|
||
glassBorderOpacity: glassBorderOpacity ?? this.glassBorderOpacity,
|
||
isDark: isDark ?? this.isDark,
|
||
accent: accent ?? this.accent,
|
||
accentLight: accentLight ?? this.accentLight,
|
||
successColor: successColor ?? this.successColor,
|
||
errorColor: errorColor ?? this.errorColor,
|
||
warningColor: warningColor ?? this.warningColor,
|
||
infoColor: infoColor ?? this.infoColor,
|
||
destructiveColor: destructiveColor ?? this.destructiveColor,
|
||
textOnAccent: textOnAccent ?? this.textOnAccent,
|
||
isAmoled: isAmoled ?? this.isAmoled,
|
||
glassBlurMultiplier: glassBlurMultiplier ?? this.glassBlurMultiplier,
|
||
cardStyleId: cardStyleId ?? this.cardStyleId,
|
||
cornerRadiusId: cornerRadiusId ?? this.cornerRadiusId,
|
||
fontScale: fontScale ?? this.fontScale,
|
||
fontWeight: fontWeight ?? this.fontWeight,
|
||
fontFamily: fontFamily ?? this.fontFamily,
|
||
);
|
||
}
|
||
|
||
@override
|
||
AppThemeExtension lerp(covariant AppThemeExtension? other, double t) {
|
||
if (other == null) return this;
|
||
return AppThemeExtension(
|
||
bgPrimary: Color.lerp(bgPrimary, other.bgPrimary, t)!,
|
||
bgSecondary: Color.lerp(bgSecondary, other.bgSecondary, t)!,
|
||
bgCard: Color.lerp(bgCard, other.bgCard, t)!,
|
||
bgElevated: Color.lerp(bgElevated, other.bgElevated, t)!,
|
||
textPrimary: Color.lerp(textPrimary, other.textPrimary, t)!,
|
||
textSecondary: Color.lerp(textSecondary, other.textSecondary, t)!,
|
||
textHint: Color.lerp(textHint, other.textHint, t)!,
|
||
textDisabled: Color.lerp(textDisabled, other.textDisabled, t)!,
|
||
textInverse: Color.lerp(textInverse, other.textInverse, t)!,
|
||
iconPrimary: Color.lerp(iconPrimary, other.iconPrimary, t)!,
|
||
iconSecondary: Color.lerp(iconSecondary, other.iconSecondary, t)!,
|
||
iconDisabled: Color.lerp(iconDisabled, other.iconDisabled, t)!,
|
||
overlaySubtle: Color.lerp(overlaySubtle, other.overlaySubtle, t)!,
|
||
overlayMedium: Color.lerp(overlayMedium, other.overlayMedium, t)!,
|
||
overlayStrong: Color.lerp(overlayStrong, other.overlayStrong, t)!,
|
||
ripple: Color.lerp(ripple, other.ripple, t)!,
|
||
glassColor: Color.lerp(glassColor, other.glassColor, t)!,
|
||
glassBorderOpacity: lerpDouble(
|
||
glassBorderOpacity,
|
||
other.glassBorderOpacity,
|
||
t,
|
||
),
|
||
isDark: t < 0.5 ? isDark : other.isDark,
|
||
accent: Color.lerp(accent, other.accent, t)!,
|
||
accentLight: Color.lerp(accentLight, other.accentLight, t)!,
|
||
successColor: Color.lerp(successColor, other.successColor, t)!,
|
||
errorColor: Color.lerp(errorColor, other.errorColor, t)!,
|
||
warningColor: Color.lerp(warningColor, other.warningColor, t)!,
|
||
infoColor: Color.lerp(infoColor, other.infoColor, t)!,
|
||
destructiveColor: Color.lerp(
|
||
destructiveColor,
|
||
other.destructiveColor,
|
||
t,
|
||
)!,
|
||
textOnAccent: Color.lerp(textOnAccent, other.textOnAccent, t)!,
|
||
isAmoled: t < 0.5 ? isAmoled : other.isAmoled,
|
||
glassBlurMultiplier: lerpDouble(
|
||
glassBlurMultiplier,
|
||
other.glassBlurMultiplier,
|
||
t,
|
||
),
|
||
cardStyleId: t < 0.5 ? cardStyleId : other.cardStyleId,
|
||
cornerRadiusId: t < 0.5 ? cornerRadiusId : other.cornerRadiusId,
|
||
fontScale: lerpDouble(fontScale, other.fontScale, t),
|
||
fontWeight:
|
||
FontWeight.lerp(fontWeight, other.fontWeight, t) ?? fontWeight,
|
||
fontFamily: t < 0.5 ? fontFamily : other.fontFamily,
|
||
);
|
||
}
|
||
|
||
/// 双精度插值
|
||
static double lerpDouble(double a, double b, double t) {
|
||
return a + (b - a) * t;
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// 日间扩展实例
|
||
// ============================================================
|
||
|
||
const _lightExtension = AppThemeExtension(
|
||
bgPrimary: LightColors.bgPrimary,
|
||
bgSecondary: LightColors.bgSecondary,
|
||
bgCard: LightColors.bgCard,
|
||
bgElevated: LightColors.bgElevated,
|
||
textPrimary: LightColors.textPrimary,
|
||
textSecondary: LightColors.textSecondary,
|
||
textHint: LightColors.textHint,
|
||
textDisabled: LightColors.textDisabled,
|
||
textInverse: LightColors.textInverse,
|
||
iconPrimary: LightColors.iconPrimary,
|
||
iconSecondary: LightColors.iconSecondary,
|
||
iconDisabled: LightColors.iconDisabled,
|
||
overlaySubtle: LightColors.overlaySubtle,
|
||
overlayMedium: LightColors.overlayMedium,
|
||
overlayStrong: LightColors.overlayStrong,
|
||
ripple: LightColors.ripple,
|
||
glassColor: Color(GlassTokens.glassColorLight),
|
||
glassBorderOpacity: GlassTokens.borderOpacity,
|
||
isDark: false,
|
||
);
|
||
|
||
// ============================================================
|
||
// 夜间扩展实例
|
||
// ============================================================
|
||
|
||
const _darkExtension = AppThemeExtension(
|
||
bgPrimary: DarkColors.bgPrimary,
|
||
bgSecondary: DarkColors.bgSecondary,
|
||
bgCard: DarkColors.bgCard,
|
||
bgElevated: DarkColors.bgElevated,
|
||
textPrimary: DarkColors.textPrimary,
|
||
textSecondary: DarkColors.textSecondary,
|
||
textHint: DarkColors.textHint,
|
||
textDisabled: DarkColors.textDisabled,
|
||
textInverse: DarkColors.textInverse,
|
||
iconPrimary: DarkColors.iconPrimary,
|
||
iconSecondary: DarkColors.iconSecondary,
|
||
iconDisabled: DarkColors.iconDisabled,
|
||
overlaySubtle: DarkColors.overlaySubtle,
|
||
overlayMedium: DarkColors.overlayMedium,
|
||
overlayStrong: DarkColors.overlayStrong,
|
||
ripple: DarkColors.ripple,
|
||
glassColor: Color(GlassTokens.glassColorDark),
|
||
glassBorderOpacity: GlassTokens.borderOpacity,
|
||
isDark: true,
|
||
);
|
||
|
||
// ============================================================
|
||
// AMOLED纯黑扩展实例
|
||
// ============================================================
|
||
|
||
const _amoledExtension = AppThemeExtension(
|
||
bgPrimary: AmoledColors.bgPrimary,
|
||
bgSecondary: AmoledColors.bgSecondary,
|
||
bgCard: AmoledColors.bgCard,
|
||
bgElevated: AmoledColors.bgElevated,
|
||
textPrimary: AmoledColors.textPrimary,
|
||
textSecondary: AmoledColors.textSecondary,
|
||
textHint: AmoledColors.textHint,
|
||
textDisabled: AmoledColors.textDisabled,
|
||
textInverse: AmoledColors.textInverse,
|
||
iconPrimary: AmoledColors.iconPrimary,
|
||
iconSecondary: AmoledColors.iconSecondary,
|
||
iconDisabled: AmoledColors.iconDisabled,
|
||
overlaySubtle: AmoledColors.overlaySubtle,
|
||
overlayMedium: AmoledColors.overlayMedium,
|
||
overlayStrong: AmoledColors.overlayStrong,
|
||
ripple: AmoledColors.ripple,
|
||
glassColor: Color(GlassTokens.glassColorDark),
|
||
glassBorderOpacity: GlassTokens.borderOpacity,
|
||
isDark: true,
|
||
isAmoled: true,
|
||
);
|
||
|
||
// ============================================================
|
||
// AppTheme — 对外统一入口
|
||
// ============================================================
|
||
|
||
/// 主题管理器
|
||
///
|
||
/// 提供 light / dark 两套完整 ThemeData,
|
||
/// 并挂载 AppThemeExtension 供全局访问。
|
||
class AppTheme {
|
||
AppTheme._();
|
||
|
||
/// 日间主题
|
||
static ThemeData get light => _buildLightTheme();
|
||
|
||
/// 夜间主题
|
||
static ThemeData get dark => _buildDarkTheme();
|
||
|
||
/// AMOLED纯黑主题
|
||
static ThemeData get amoled => _buildAmoledTheme();
|
||
|
||
/// 根据设置动态构建主题
|
||
static ThemeData buildFromSettings({
|
||
required bool isDark,
|
||
required bool isAmoled,
|
||
required Color accent,
|
||
required Color accentLight,
|
||
required double fontScale,
|
||
required FontWeight fontWeight,
|
||
required String fontFamily,
|
||
required double glassBlurMultiplier,
|
||
required String cardStyleId,
|
||
required String cornerRadiusId,
|
||
}) {
|
||
final baseExtension = isAmoled
|
||
? _amoledExtension
|
||
: isDark
|
||
? _darkExtension
|
||
: _lightExtension;
|
||
|
||
final dynamicExtension = baseExtension.copyWith(
|
||
accent: accent,
|
||
accentLight: accentLight,
|
||
fontScale: fontScale,
|
||
fontWeight: fontWeight,
|
||
fontFamily: fontFamily,
|
||
glassBlurMultiplier: glassBlurMultiplier,
|
||
cardStyleId: cardStyleId,
|
||
cornerRadiusId: cornerRadiusId,
|
||
);
|
||
|
||
if (isAmoled) {
|
||
return _buildAmoledTheme(extension: dynamicExtension);
|
||
} else if (isDark) {
|
||
return _buildDarkTheme(extension: dynamicExtension);
|
||
} else {
|
||
return _buildLightTheme(extension: dynamicExtension);
|
||
}
|
||
}
|
||
|
||
// ---- 从 BuildContext 获取扩展令牌 ----
|
||
|
||
/// 获取自定义主题扩展(带安全回退)
|
||
static AppThemeExtension ext(BuildContext context) {
|
||
return Theme.of(context).extension<AppThemeExtension>() ?? _lightExtension;
|
||
}
|
||
|
||
/// 是否暗色模式
|
||
static bool isDarkMode(BuildContext context) {
|
||
return ext(context).isDark;
|
||
}
|
||
|
||
// ============================================================
|
||
// 日间主题构建
|
||
// ============================================================
|
||
|
||
static ThemeData _buildLightTheme({AppThemeExtension? extension}) {
|
||
final ext = extension ?? _lightExtension;
|
||
final colorScheme = ColorScheme.light(
|
||
primary: ext.accent,
|
||
primaryContainer: ext.accentLight,
|
||
onPrimaryContainer: LightColors.textPrimary,
|
||
secondary: LightColors.secondary,
|
||
secondaryContainer: LightColors.secondary.withValues(alpha: 0.15),
|
||
onSecondaryContainer: LightColors.secondary,
|
||
tertiary: LightColors.accent,
|
||
error: LightColors.error,
|
||
surface: LightColors.bgPrimary,
|
||
onSurface: LightColors.textPrimary,
|
||
surfaceContainerHighest: LightColors.bgSecondary,
|
||
);
|
||
|
||
return _buildBaseTheme(colorScheme: colorScheme, extension: ext);
|
||
}
|
||
|
||
// ============================================================
|
||
// 夜间主题构建
|
||
// ============================================================
|
||
|
||
static ThemeData _buildDarkTheme({AppThemeExtension? extension}) {
|
||
final ext = extension ?? _darkExtension;
|
||
final colorScheme = ColorScheme.dark(
|
||
primary: ext.accent,
|
||
onPrimary: DarkColors.textInverse,
|
||
primaryContainer: ext.accentLight,
|
||
onPrimaryContainer: DarkColors.textPrimary,
|
||
secondary: DarkColors.secondary,
|
||
onSecondary: DarkColors.textInverse,
|
||
secondaryContainer: DarkColors.secondary.withValues(alpha: 0.15),
|
||
onSecondaryContainer: DarkColors.secondary,
|
||
tertiary: DarkColors.accent,
|
||
onTertiary: DarkColors.textInverse,
|
||
error: DarkColors.error,
|
||
onError: DarkColors.textInverse,
|
||
surface: DarkColors.bgPrimary,
|
||
onSurface: DarkColors.textPrimary,
|
||
surfaceContainerHighest: DarkColors.bgSecondary,
|
||
);
|
||
|
||
return _buildBaseTheme(colorScheme: colorScheme, extension: ext);
|
||
}
|
||
|
||
// ============================================================
|
||
// AMOLED纯黑主题构建
|
||
// ============================================================
|
||
|
||
static ThemeData _buildAmoledTheme({AppThemeExtension? extension}) {
|
||
final ext = extension ?? _amoledExtension;
|
||
final colorScheme = ColorScheme.dark(
|
||
primary: ext.accent,
|
||
primaryContainer: ext.accentLight,
|
||
onPrimaryContainer: AmoledColors.textPrimary,
|
||
secondary: AmoledColors.secondary,
|
||
secondaryContainer: AmoledColors.secondary.withValues(alpha: 0.15),
|
||
onSecondaryContainer: AmoledColors.secondary,
|
||
tertiary: AmoledColors.accent,
|
||
onTertiary: AmoledColors.textInverse,
|
||
error: AmoledColors.error,
|
||
surface: AmoledColors.bgPrimary,
|
||
onSurface: AmoledColors.textPrimary,
|
||
surfaceContainerHighest: AmoledColors.bgSecondary,
|
||
);
|
||
|
||
return _buildBaseTheme(colorScheme: colorScheme, extension: ext);
|
||
}
|
||
|
||
// ============================================================
|
||
// 基础主题骨架 (Cupertino 风格)
|
||
// ============================================================
|
||
|
||
static ThemeData _buildBaseTheme({
|
||
required ColorScheme colorScheme,
|
||
required AppThemeExtension extension,
|
||
}) {
|
||
return ThemeData(
|
||
useMaterial3: true,
|
||
colorScheme: colorScheme,
|
||
extensions: [extension],
|
||
|
||
// ---- 字体 ----
|
||
fontFamily: extension.fontFamily,
|
||
textTheme: _buildTextTheme(
|
||
colorScheme,
|
||
extension.fontScale,
|
||
extension.fontWeight,
|
||
extension.fontFamily,
|
||
),
|
||
|
||
// ---- Cupertino 交互 ----
|
||
cupertinoOverrideTheme: CupertinoThemeData(
|
||
primaryColor: colorScheme.primary,
|
||
scaffoldBackgroundColor: colorScheme.surface,
|
||
barBackgroundColor: extension.bgElevated,
|
||
textTheme: CupertinoTextThemeData(
|
||
primaryColor: colorScheme.primary,
|
||
textStyle: AppTypography.body.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
navTitleTextStyle: AppTypography.title3.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
navLargeTitleTextStyle: AppTypography.title1.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
navActionTextStyle: AppTypography.subhead.copyWith(
|
||
color: colorScheme.primary,
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
actionTextStyle: AppTypography.subhead.copyWith(
|
||
color: colorScheme.primary,
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
tabLabelTextStyle: AppTypography.caption1.copyWith(
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
pickerTextStyle: AppTypography.body.copyWith(
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
dateTimePickerTextStyle: AppTypography.body.copyWith(
|
||
fontFamily: extension.fontFamily,
|
||
),
|
||
),
|
||
),
|
||
|
||
// ---- Scaffold ----
|
||
scaffoldBackgroundColor: colorScheme.surface,
|
||
|
||
// ---- AppBar ----
|
||
appBarTheme: AppBarTheme(
|
||
elevation: 0,
|
||
scrolledUnderElevation: 0,
|
||
centerTitle: true,
|
||
backgroundColor: colorScheme.surface.withValues(alpha: 0.85),
|
||
foregroundColor: colorScheme.onSurface,
|
||
titleTextStyle: AppTypography.title3.copyWith(
|
||
color: colorScheme.onSurface,
|
||
),
|
||
),
|
||
|
||
// ---- 底部导航栏 ----
|
||
navigationBarTheme: NavigationBarThemeData(
|
||
elevation: 0,
|
||
backgroundColor: extension.bgElevated.withValues(alpha: 0.85),
|
||
indicatorColor: colorScheme.primary.withValues(alpha: 0.15),
|
||
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
||
height: 64,
|
||
),
|
||
|
||
// ---- 卡片 ----
|
||
cardTheme: CardThemeData(
|
||
elevation: 0,
|
||
color: extension.bgCard,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: AppRadius.lgBorder,
|
||
side: BorderSide(
|
||
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
|
||
width: 0.5,
|
||
),
|
||
),
|
||
margin: EdgeInsets.zero,
|
||
),
|
||
|
||
// ---- 输入框 ----
|
||
inputDecorationTheme: InputDecorationTheme(
|
||
filled: true,
|
||
fillColor: extension.bgSecondary,
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.md,
|
||
vertical: AppSpacing.sm,
|
||
),
|
||
border: OutlineInputBorder(
|
||
borderRadius: AppRadius.mdBorder,
|
||
borderSide: BorderSide.none,
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderRadius: AppRadius.mdBorder,
|
||
borderSide: BorderSide.none,
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: AppRadius.mdBorder,
|
||
borderSide: BorderSide(color: colorScheme.primary, width: 1.5),
|
||
),
|
||
hintStyle: AppTypography.body.copyWith(color: extension.textHint),
|
||
),
|
||
|
||
// ---- 按钮 ----
|
||
filledButtonTheme: FilledButtonThemeData(
|
||
style: FilledButton.styleFrom(
|
||
backgroundColor: colorScheme.primary,
|
||
foregroundColor: colorScheme.onPrimary,
|
||
textStyle: AppTypography.callout,
|
||
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorder),
|
||
minimumSize: const Size(double.infinity, 48),
|
||
),
|
||
),
|
||
|
||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||
style: OutlinedButton.styleFrom(
|
||
foregroundColor: colorScheme.primary,
|
||
textStyle: AppTypography.callout,
|
||
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorder),
|
||
side: BorderSide(color: colorScheme.primary, width: 1.2),
|
||
minimumSize: const Size(double.infinity, 48),
|
||
),
|
||
),
|
||
|
||
textButtonTheme: TextButtonThemeData(
|
||
style: TextButton.styleFrom(
|
||
foregroundColor: colorScheme.primary,
|
||
textStyle: AppTypography.subhead.copyWith(
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
|
||
// ---- 底部弹窗 ----
|
||
bottomSheetTheme: BottomSheetThemeData(
|
||
backgroundColor: extension.bgElevated,
|
||
shape: const RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.vertical(
|
||
top: Radius.circular(AppRadius.xl),
|
||
),
|
||
),
|
||
showDragHandle: true,
|
||
),
|
||
|
||
// ---- 对话框 ----
|
||
dialogTheme: DialogThemeData(
|
||
backgroundColor: extension.bgElevated,
|
||
shape: RoundedRectangleBorder(borderRadius: AppRadius.lgBorder),
|
||
),
|
||
|
||
// ---- 分割线 ----
|
||
dividerTheme: DividerThemeData(
|
||
color: colorScheme.outlineVariant.withValues(alpha: 0.3),
|
||
thickness: 0.5,
|
||
space: 0,
|
||
),
|
||
|
||
// ---- Chip ----
|
||
chipTheme: ChipThemeData(
|
||
backgroundColor: extension.bgSecondary,
|
||
selectedColor: colorScheme.primary.withValues(alpha: 0.15),
|
||
labelStyle: AppTypography.caption1.copyWith(
|
||
color: colorScheme.onSurface,
|
||
),
|
||
shape: RoundedRectangleBorder(borderRadius: AppRadius.fullBorder),
|
||
side: BorderSide.none,
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: AppSpacing.sm,
|
||
vertical: AppSpacing.xs,
|
||
),
|
||
),
|
||
|
||
// ---- 滚动 ----
|
||
scrollbarTheme: ScrollbarThemeData(
|
||
thumbColor: WidgetStateProperty.all(
|
||
colorScheme.primary.withValues(alpha: 0.3),
|
||
),
|
||
radius: const Radius.circular(AppRadius.full),
|
||
thickness: WidgetStateProperty.all(4),
|
||
),
|
||
|
||
// ---- 页面路由过渡 (iOS 风格) ----
|
||
pageTransitionsTheme: const PageTransitionsTheme(
|
||
builders: {
|
||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// TextTheme
|
||
// ============================================================
|
||
|
||
static TextTheme _buildTextTheme(
|
||
ColorScheme colorScheme, [
|
||
double fontScale = 1.0,
|
||
FontWeight baseWeight = FontWeight.w400,
|
||
String fontFamily = 'Inter',
|
||
]) {
|
||
FontWeight adjustWeight(FontWeight target) {
|
||
final diff = baseWeight.value - FontWeight.w400.value;
|
||
final newValue = (target.value + diff).clamp(100, 900);
|
||
final idx = (newValue ~/ 100) - 1;
|
||
return FontWeight.values[idx.clamp(0, 8)];
|
||
}
|
||
|
||
return TextTheme(
|
||
displayLarge: AppTypography.display.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontDisplay * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.bold),
|
||
fontFamily: fontFamily,
|
||
),
|
||
headlineLarge: AppTypography.title1.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontTitle1 * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.bold),
|
||
fontFamily: fontFamily,
|
||
),
|
||
headlineMedium: AppTypography.title2.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontTitle2 * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.bold),
|
||
fontFamily: fontFamily,
|
||
),
|
||
headlineSmall: AppTypography.title3.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontTitle3 * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.w600),
|
||
fontFamily: fontFamily,
|
||
),
|
||
titleLarge: AppTypography.headline.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontHeadline * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.w600),
|
||
fontFamily: fontFamily,
|
||
),
|
||
bodyLarge: AppTypography.body.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontBody * fontScale,
|
||
fontWeight: baseWeight,
|
||
fontFamily: fontFamily,
|
||
),
|
||
bodyMedium: AppTypography.subhead.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontSubhead * fontScale,
|
||
fontWeight: baseWeight,
|
||
fontFamily: fontFamily,
|
||
),
|
||
bodySmall: AppTypography.footnote.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontFootnote * fontScale,
|
||
fontFamily: fontFamily,
|
||
),
|
||
labelLarge: AppTypography.callout.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontCallout * fontScale,
|
||
fontWeight: adjustWeight(FontWeight.w500),
|
||
fontFamily: fontFamily,
|
||
),
|
||
labelMedium: AppTypography.caption1.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontCaption1 * fontScale,
|
||
fontFamily: fontFamily,
|
||
),
|
||
labelSmall: AppTypography.caption2.copyWith(
|
||
color: colorScheme.onSurface,
|
||
fontSize: AppTypography.fontCaption2 * fontScale,
|
||
fontFamily: fontFamily,
|
||
),
|
||
);
|
||
}
|
||
}
|