xworkmate-app/lib/theme/app_theme.dart
2026-03-11 15:34:15 +08:00

213 lines
7.0 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'app_palette.dart';
class AppTheme {
static ThemeData light() =>
_theme(brightness: Brightness.light, palette: AppPalette.light);
static ThemeData dark() =>
_theme(brightness: Brightness.dark, palette: AppPalette.dark);
static ThemeData _theme({
required Brightness brightness,
required AppPalette palette,
}) {
final platform = defaultTargetPlatform;
final isDesktop =
platform == TargetPlatform.macOS ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.linux;
final colorScheme =
ColorScheme.fromSeed(
seedColor: palette.accent,
brightness: brightness,
surface: palette.surfacePrimary,
).copyWith(
primary: palette.accent,
onPrimary: Colors.white,
secondary: palette.accent,
onSecondary: Colors.white,
tertiary: palette.success,
onTertiary: Colors.white,
error: palette.danger,
onError: Colors.white,
surface: palette.surfacePrimary,
onSurface: palette.textPrimary,
surfaceContainerHighest: palette.surfaceSecondary,
outline: palette.stroke,
outlineVariant: palette.strokeSoft,
inverseSurface: palette.textPrimary,
onInverseSurface: palette.surfacePrimary,
shadow: palette.shadow,
scrim: Colors.black.withValues(
alpha: brightness == Brightness.dark ? 0.62 : 0.14,
),
);
final base = ThemeData(
useMaterial3: true,
brightness: brightness,
typography: Typography.material2021(platform: platform),
colorScheme: colorScheme,
scaffoldBackgroundColor: palette.canvas,
extensions: [palette],
);
final tunedTextTheme = _textTheme(
base.textTheme,
palette: palette,
isDesktop: isDesktop,
);
return base.copyWith(
splashFactory: NoSplash.splashFactory,
dividerColor: palette.strokeSoft,
hoverColor: palette.hover,
textTheme: tunedTextTheme,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
surfaceTintColor: Colors.transparent,
),
cardTheme: CardThemeData(
elevation: 0,
color: palette.surfacePrimary,
margin: EdgeInsets.zero,
shadowColor: palette.shadow,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: BorderSide(color: palette.strokeSoft),
),
),
chipTheme: base.chipTheme.copyWith(
backgroundColor: palette.surfaceSecondary,
side: BorderSide(color: palette.strokeSoft),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
),
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
foregroundColor: palette.textSecondary,
backgroundColor: palette.surfaceSecondary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: palette.strokeSoft),
),
padding: const EdgeInsets.all(12),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: palette.surfaceSecondary,
hintStyle: tunedTextTheme.bodyMedium?.copyWith(
color: palette.textMuted,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: palette.strokeSoft),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: palette.strokeSoft),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide(color: palette.accent.withValues(alpha: 0.42)),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
side: WidgetStatePropertyAll(BorderSide(color: palette.strokeSoft)),
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return palette.surfacePrimary;
}
return palette.surfaceSecondary;
}),
foregroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return palette.textPrimary;
}
return palette.textSecondary;
}),
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: palette.surfaceTertiary,
contentTextStyle: TextStyle(color: palette.textPrimary),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
);
}
static TextTheme _textTheme(
TextTheme base, {
required AppPalette palette,
required bool isDesktop,
}) {
return base.copyWith(
displaySmall: base.displaySmall?.copyWith(
fontSize: isDesktop ? 32 : 34,
fontWeight: FontWeight.w600,
letterSpacing: -0.9,
),
headlineSmall: base.headlineSmall?.copyWith(
fontSize: isDesktop ? 22 : 24,
fontWeight: FontWeight.w600,
letterSpacing: -0.45,
),
titleLarge: base.titleLarge?.copyWith(
fontSize: isDesktop ? 18 : 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
),
titleMedium: base.titleMedium?.copyWith(
fontSize: isDesktop ? 15 : 16,
fontWeight: FontWeight.w600,
),
titleSmall: base.titleSmall?.copyWith(
fontSize: isDesktop ? 13 : 14,
fontWeight: FontWeight.w600,
),
bodyLarge: base.bodyLarge?.copyWith(
fontSize: isDesktop ? 14 : 15,
height: 1.45,
color: palette.textPrimary,
),
bodyMedium: base.bodyMedium?.copyWith(
fontSize: isDesktop ? 13 : 14,
height: 1.4,
color: palette.textSecondary,
),
bodySmall: base.bodySmall?.copyWith(
fontSize: isDesktop ? 12 : 12,
height: 1.35,
color: palette.textMuted,
),
labelLarge: base.labelLarge?.copyWith(
fontSize: isDesktop ? 13 : 14,
fontWeight: FontWeight.w600,
),
labelMedium: base.labelMedium?.copyWith(
fontSize: isDesktop ? 12 : 12,
fontWeight: FontWeight.w600,
),
labelSmall: base.labelSmall?.copyWith(fontSize: isDesktop ? 11 : 11),
);
}
}