Files
ReelShort/lib/core/theme/app_theme.dart
T

281 lines
10 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app_colors.dart';
import 'app_dimens.dart';
/// 深色视觉主题(默认且唯一)。
abstract final class AppTheme {
/// 页面背景:顶部暖黑、底部纯黑,避免旧深蓝渐变。
static const LinearGradient pageGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.mineGradientTop,
AppColors.mineGradientMiddle,
AppColors.mineGradientBottom,
],
stops: [0, 0.4, 0.75],
);
/// 卡片阴影关闭,改用底色与分割线。
static const BoxShadow cardShadow = BoxShadow(
color: AppColors.cardShadow,
blurRadius: AppDimens.cardShadowBlur,
offset: Offset(0, AppDimens.cardShadowY),
);
static final BorderRadius _buttonRadius = BorderRadius.circular(
AppDimens.buttonRadius,
);
static final BorderRadius _cardRadius = BorderRadius.circular(
AppDimens.cardRadius,
);
static final BorderRadius _inputRadius = BorderRadius.circular(
AppDimens.inputRadius,
);
/// 全局深色主题。
static ThemeData dark() {
const scheme = ColorScheme.dark(
primary: AppColors.brandRed,
onPrimary: AppColors.onPrimary,
secondary: AppColors.secondary,
onSecondary: AppColors.ink,
primaryContainer: AppColors.raised,
onPrimaryContainer: AppColors.onSurface,
secondaryContainer: AppColors.badgePink,
onSecondaryContainer: AppColors.badgePinkText,
surface: AppColors.ink,
onSurface: AppColors.onSurface,
surfaceContainerHighest: AppColors.raised,
outline: AppColors.border,
outlineVariant: AppColors.border,
surfaceTint: AppColors.transparent,
inversePrimary: AppColors.brandRedPressed,
error: AppColors.error,
onError: AppColors.onPrimary,
);
final base = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: scheme,
scaffoldBackgroundColor: AppColors.ink,
canvasColor: AppColors.ink,
fontFamily: 'PlusJakartaSans',
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColors.brandRed,
),
sliderTheme: const SliderThemeData(
activeTrackColor: AppColors.brandRed,
inactiveTrackColor: AppColors.border,
thumbColor: AppColors.onPrimary,
),
tabBarTheme: const TabBarThemeData(
labelColor: AppColors.brandRed,
indicatorColor: AppColors.brandRed,
unselectedLabelColor: AppColors.muted,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
shape: RoundedRectangleBorder(borderRadius: _buttonRadius),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith(_buttonColor),
foregroundColor: const WidgetStatePropertyAll(AppColors.onPrimary),
elevation: const WidgetStatePropertyAll(0),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: _buttonRadius),
),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith(_buttonColor),
foregroundColor: const WidgetStatePropertyAll(AppColors.onPrimary),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: _buttonRadius),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
foregroundColor: const WidgetStatePropertyAll(AppColors.textPrimary),
minimumSize: const WidgetStatePropertyAll(
Size(0, AppDimens.buttonHeight),
),
side: const WidgetStatePropertyAll(
BorderSide(color: AppColors.mineOutline),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: _buttonRadius),
),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.input,
hintStyle: const TextStyle(color: AppColors.subtle),
labelStyle: const TextStyle(color: AppColors.muted),
border: OutlineInputBorder(
borderRadius: _inputRadius,
borderSide: const BorderSide(color: AppColors.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: _inputRadius,
borderSide: const BorderSide(color: AppColors.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: _inputRadius,
borderSide: const BorderSide(color: AppColors.brandRed),
),
),
chipTheme: ChipThemeData(
backgroundColor: AppColors.raised,
selectedColor: AppColors.brandRed.withValues(alpha: 0.22),
labelStyle: const TextStyle(color: AppColors.onSurface),
secondaryLabelStyle: const TextStyle(color: AppColors.muted),
side: const BorderSide(color: AppColors.border),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppDimens.chipRadius),
),
),
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.transparent,
foregroundColor: AppColors.textPrimary,
elevation: 0,
scrolledUnderElevation: 0,
surfaceTintColor: AppColors.transparent,
systemOverlayStyle: SystemUiOverlayStyle.light,
titleTextStyle: TextStyle(
color: AppColors.textPrimary,
fontFamily: 'Outfit',
fontSize: AppDimens.pageTitle,
fontWeight: FontWeight.w800,
),
),
iconTheme: const IconThemeData(color: AppColors.textPrimary),
dividerTheme: const DividerThemeData(
color: AppColors.border,
thickness: 1,
space: 1,
),
cardTheme: CardThemeData(
color: AppColors.card,
elevation: 0,
shadowColor: AppColors.transparent,
surfaceTintColor: AppColors.transparent,
shape: RoundedRectangleBorder(
borderRadius: _cardRadius,
side: const BorderSide(color: AppColors.border),
),
),
dialogTheme: DialogThemeData(
backgroundColor: AppColors.raised,
surfaceTintColor: AppColors.transparent,
shape: RoundedRectangleBorder(borderRadius: _cardRadius),
titleTextStyle: const TextStyle(
color: AppColors.onSurface,
fontFamily: 'Outfit',
fontSize: AppDimens.sectionTitle,
fontWeight: FontWeight.w700,
),
contentTextStyle: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.body,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.navBackground,
selectedItemColor: AppColors.navSelected,
unselectedItemColor: AppColors.navUnselected,
elevation: 0,
type: BottomNavigationBarType.fixed,
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: AppColors.transparent,
modalBackgroundColor: AppColors.transparent,
surfaceTintColor: AppColors.transparent,
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
? AppColors.onPrimary
: null,
),
trackColor: WidgetStateProperty.resolveWith(
(states) =>
states.contains(WidgetState.selected) ? AppColors.brandRed : null,
),
),
snackBarTheme: const SnackBarThemeData(
backgroundColor: AppColors.raised,
contentTextStyle: TextStyle(color: AppColors.onSurface),
actionTextColor: AppColors.secondary,
),
listTileTheme: const ListTileThemeData(
iconColor: AppColors.onSurface,
textColor: AppColors.onSurface,
),
);
final textTheme = base.textTheme
.apply(
fontFamily: 'PlusJakartaSans',
bodyColor: AppColors.cream,
displayColor: AppColors.onSurface,
)
.copyWith(
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),
bodyLarge: base.textTheme.bodyLarge?.copyWith(
color: AppColors.onSurface,
),
bodyMedium: base.textTheme.bodyMedium?.copyWith(
color: AppColors.onSurface,
),
bodySmall: base.textTheme.bodySmall?.copyWith(color: AppColors.muted),
labelLarge: base.textTheme.labelLarge?.copyWith(
color: AppColors.onSurface,
),
labelMedium: base.textTheme.labelMedium?.copyWith(
color: AppColors.muted,
),
labelSmall: base.textTheme.labelSmall?.copyWith(
color: AppColors.subtle,
),
);
return base.copyWith(textTheme: textTheme);
}
/// 兼容旧调用点,统一落到深色主题。
static ThemeData light() => dark();
static TextStyle? _display(TextStyle? style) =>
style?.copyWith(fontFamily: 'Outfit', color: AppColors.onSurface);
static Color _buttonColor(Set<WidgetState> states) {
if (states.contains(WidgetState.pressed) ||
states.contains(WidgetState.hovered) ||
states.contains(WidgetState.focused)) {
return AppColors.brandRedPressed;
}
return AppColors.brandRed;
}
}