281 lines
9.9 KiB
Dart
281 lines
9.9 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.blue,
|
|
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.bluePressed,
|
|
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.blue,
|
|
),
|
|
sliderTheme: const SliderThemeData(
|
|
activeTrackColor: AppColors.blue,
|
|
inactiveTrackColor: AppColors.border,
|
|
thumbColor: AppColors.onPrimary,
|
|
),
|
|
tabBarTheme: const TabBarThemeData(
|
|
labelColor: AppColors.blue,
|
|
indicatorColor: AppColors.blue,
|
|
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.blue),
|
|
),
|
|
),
|
|
chipTheme: ChipThemeData(
|
|
backgroundColor: AppColors.raised,
|
|
selectedColor: AppColors.blue.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.blue : 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.bluePressed;
|
|
}
|
|
return AppColors.blue;
|
|
}
|
|
}
|