230 lines
8.6 KiB
Dart
230 lines
8.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'app_colors.dart';
|
|
import 'app_typography.dart';
|
|
import 'branding_config.dart';
|
|
|
|
class AppTheme {
|
|
AppTheme._();
|
|
|
|
static ThemeData light({BrandingConfig? branding}) {
|
|
final primary = branding?.primaryColor ?? AppColors.primary;
|
|
final secondary = branding?.secondaryColor ?? AppColors.secondary;
|
|
|
|
final colorScheme = ColorScheme.fromSeed(
|
|
seedColor: primary,
|
|
secondary: secondary,
|
|
brightness: Brightness.light,
|
|
surface: AppColors.lightSurface,
|
|
);
|
|
|
|
return _buildTheme(colorScheme, Brightness.light);
|
|
}
|
|
|
|
static ThemeData dark({BrandingConfig? branding}) {
|
|
final primary = branding?.primaryColor ?? AppColors.primary;
|
|
final secondary = branding?.secondaryColor ?? AppColors.secondary;
|
|
|
|
final colorScheme = ColorScheme.fromSeed(
|
|
seedColor: primary,
|
|
secondary: secondary,
|
|
brightness: Brightness.dark,
|
|
surface: AppColors.darkSurface,
|
|
);
|
|
|
|
return _buildTheme(colorScheme, Brightness.dark);
|
|
}
|
|
|
|
/// Theme for white cards, form fields, and picker sheets (unchanged in dark mode).
|
|
static ThemeData cardContentTheme(ThemeData theme) {
|
|
final scheme = theme.brightness == Brightness.light
|
|
? theme.colorScheme
|
|
: ColorScheme.fromSeed(
|
|
seedColor: theme.colorScheme.primary,
|
|
secondary: theme.colorScheme.secondary,
|
|
brightness: Brightness.light,
|
|
surface: AppColors.card,
|
|
);
|
|
|
|
final base = theme.brightness == Brightness.light
|
|
? theme
|
|
: theme.copyWith(
|
|
brightness: Brightness.light,
|
|
colorScheme: scheme,
|
|
textTheme: AppTypography.textTheme(scheme),
|
|
primaryTextTheme: AppTypography.textTheme(scheme),
|
|
);
|
|
|
|
return base.copyWith(
|
|
scaffoldBackgroundColor: AppColors.card,
|
|
inputDecorationTheme: whiteInputDecorationTheme(scheme),
|
|
bottomSheetTheme: const BottomSheetThemeData(
|
|
backgroundColor: AppColors.card,
|
|
surfaceTintColor: Colors.transparent,
|
|
dragHandleColor: Color(0xFFBDBDBD),
|
|
),
|
|
listTileTheme: ListTileThemeData(
|
|
tileColor: AppColors.card,
|
|
selectedTileColor: scheme.primary.withValues(alpha: 0.08),
|
|
textColor: scheme.onSurface,
|
|
iconColor: scheme.onSurfaceVariant,
|
|
),
|
|
dividerTheme: DividerThemeData(
|
|
color: scheme.outline.withValues(alpha: 0.15),
|
|
),
|
|
);
|
|
}
|
|
|
|
static InputDecorationTheme whiteInputDecorationTheme(ColorScheme scheme) {
|
|
final textTheme = AppTypography.textTheme(scheme);
|
|
|
|
return InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: AppColors.card,
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: scheme.outline.withValues(alpha: 0.35)),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: scheme.primary, width: 2),
|
|
),
|
|
disabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: scheme.outline.withValues(alpha: 0.2)),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: scheme.error),
|
|
),
|
|
focusedErrorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: scheme.error, width: 2),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
labelStyle: textTheme.bodyMedium,
|
|
hintStyle: textTheme.bodyMedium?.copyWith(color: scheme.onSurfaceVariant),
|
|
helperStyle: textTheme.bodySmall,
|
|
errorStyle: textTheme.bodySmall?.copyWith(color: scheme.error),
|
|
);
|
|
}
|
|
|
|
static ThemeData _buildTheme(ColorScheme colorScheme, Brightness brightness) {
|
|
final isDark = brightness == Brightness.dark;
|
|
final textTheme = AppTypography.textTheme(colorScheme);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
fontFamily: AppTypography.fontFamily,
|
|
colorScheme: colorScheme,
|
|
brightness: brightness,
|
|
textTheme: textTheme,
|
|
primaryTextTheme: textTheme,
|
|
scaffoldBackgroundColor:
|
|
isDark ? AppColors.darkBackground : AppColors.lightBackground,
|
|
appBarTheme: AppBarTheme(
|
|
centerTitle: false,
|
|
elevation: 0,
|
|
scrolledUnderElevation: 1,
|
|
backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
|
foregroundColor: isDark ? AppColors.textOnDark : AppColors.textPrimary,
|
|
titleTextStyle: textTheme.titleLarge,
|
|
toolbarTextStyle: textTheme.bodyLarge,
|
|
),
|
|
cardTheme: CardThemeData(
|
|
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: isDark ? 2 : 1,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
clipBehavior: Clip.antiAlias,
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: isDark
|
|
? colorScheme.surfaceContainerHighest
|
|
: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: colorScheme.outline.withValues(alpha: 0.5)),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(color: colorScheme.primary, width: 2),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
labelStyle: textTheme.bodyMedium,
|
|
hintStyle: textTheme.bodyMedium?.copyWith(color: colorScheme.onSurfaceVariant),
|
|
helperStyle: textTheme.bodySmall,
|
|
errorStyle: textTheme.bodySmall?.copyWith(color: colorScheme.error),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
minimumSize: const Size(0, 48),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
textStyle: textTheme.labelLarge,
|
|
),
|
|
),
|
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
style: OutlinedButton.styleFrom(
|
|
minimumSize: const Size(0, 48),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
textStyle: textTheme.labelLarge,
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
minimumSize: const Size(0, 48),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
textStyle: textTheme.labelLarge,
|
|
),
|
|
),
|
|
navigationRailTheme: NavigationRailThemeData(
|
|
backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
|
selectedIconTheme: IconThemeData(color: colorScheme.primary),
|
|
selectedLabelTextStyle: textTheme.labelMedium?.copyWith(color: colorScheme.primary),
|
|
unselectedLabelTextStyle: textTheme.labelMedium,
|
|
),
|
|
drawerTheme: DrawerThemeData(
|
|
backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
|
),
|
|
dividerTheme: DividerThemeData(
|
|
color: colorScheme.outline.withValues(alpha: 0.2),
|
|
),
|
|
snackBarTheme: SnackBarThemeData(
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
contentTextStyle: textTheme.bodyMedium?.copyWith(color: colorScheme.onInverseSurface),
|
|
),
|
|
dataTableTheme: DataTableThemeData(
|
|
headingRowColor: WidgetStateProperty.all(
|
|
colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
|
),
|
|
headingTextStyle: textTheme.labelLarge,
|
|
dataTextStyle: textTheme.bodyMedium,
|
|
),
|
|
chipTheme: ChipThemeData(
|
|
labelStyle: textTheme.labelSmall,
|
|
),
|
|
dialogTheme: DialogThemeData(
|
|
titleTextStyle: textTheme.titleLarge,
|
|
contentTextStyle: textTheme.bodyMedium,
|
|
),
|
|
listTileTheme: ListTileThemeData(
|
|
titleTextStyle: textTheme.bodyLarge,
|
|
subtitleTextStyle: textTheme.bodySmall,
|
|
),
|
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
|
selectedLabelStyle: textTheme.labelSmall,
|
|
unselectedLabelStyle: textTheme.labelSmall,
|
|
),
|
|
tabBarTheme: TabBarThemeData(
|
|
labelStyle: textTheme.labelLarge,
|
|
unselectedLabelStyle: textTheme.labelMedium,
|
|
),
|
|
);
|
|
}
|
|
}
|