MotorSignal-App/lib/core/theme/theme.dart

403 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'app_colors.dart';
import 'app_spacing.dart';
import 'app_typography.dart';
import 'theme_extensions.dart';
/// Authoritative Material 3 theme definitions for MotorSignal.
class AppTheme {
/// Dark Theme (Cockpit OLED).
static ThemeData get darkTheme {
const colorScheme = ColorScheme.dark(
primary: AppColors.darkPrimary,
onPrimary: AppColors.onBrand,
primaryContainer: AppColors.brandContainerDark,
onPrimaryContainer: AppColors.onBrandContainerDark,
secondary: AppColors.darkSecondary,
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFF311414),
onSecondaryContainer: Color(0xFFFFDAD6),
surface: AppColors.darkSurface,
onSurface: AppColors.darkOnSurface,
onSurfaceVariant: AppColors.darkOnSurfaceVariant,
outline: AppColors.darkOutline,
outlineVariant: AppColors.darkOutlineVariant,
error: AppColors.semanticDanger,
onError: Color(0xFFFFFFFF),
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: colorScheme,
scaffoldBackgroundColor: AppColors.darkBackground,
fontFamily: AppTypography.fontBody,
extensions: const [
MotorSignalThemeColors.dark,
],
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
scrolledUnderElevation: 0,
titleTextStyle: AppTypography.headlineSm,
iconTheme: IconThemeData(color: AppColors.darkOnSurface),
),
cardTheme: CardThemeData(
color: AppColors.darkSurfaceContainer,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.lgBorderRadius,
side: const BorderSide(color: AppColors.darkOutlineVariant, width: 0.8),
),
),
dialogTheme: DialogThemeData(
backgroundColor: AppColors.darkSurfaceContainerHigh,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.lgBorderRadius,
side: const BorderSide(color: AppColors.darkOutlineVariant, width: 0.8),
),
titleTextStyle: AppTypography.headlineMd.copyWith(color: AppColors.darkOnSurface),
contentTextStyle: AppTypography.bodyMd.copyWith(color: AppColors.darkOnSurfaceVariant),
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: AppColors.darkSurfaceContainerHigh,
modalBackgroundColor: AppColors.darkSurfaceContainerHigh,
elevation: 12,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(AppRadius.xl)),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: WidgetStateProperty.all(
RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
),
side: WidgetStateProperty.resolveWith<BorderSide>((states) {
if (states.contains(WidgetState.selected)) {
return const BorderSide(color: AppColors.darkPrimary, width: 1.2);
}
return const BorderSide(color: AppColors.darkOutlineVariant, width: 0.8);
}),
backgroundColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.darkPrimary.withValues(alpha: 0.12);
}
return AppColors.darkSurfaceContainer;
}),
foregroundColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.darkPrimary;
}
return AppColors.darkOnSurfaceVariant;
}),
textStyle: WidgetStateProperty.all(AppTypography.button),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: AppColors.darkPrimary,
foregroundColor: const Color(0xFF0D0F13),
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.darkSurfaceContainerHigh,
foregroundColor: AppColors.darkOnSurface,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
elevation: 1,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.darkPrimary,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
side: const BorderSide(color: AppColors.darkOutline, width: 1),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.darkPrimary,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
textStyle: AppTypography.button,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.darkSurfaceContainer,
contentPadding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
border: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.darkOutline, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.darkOutline, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.cyberCyan, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.hazardRed, width: 1.5),
),
labelStyle: AppTypography.bodyMd.copyWith(color: AppColors.darkOnSurfaceVariant),
hintStyle: AppTypography.bodyMd.copyWith(color: AppColors.darkOnSurfaceVariant.withValues(alpha: 0.6)),
),
chipTheme: ChipThemeData(
backgroundColor: AppColors.darkSurfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorderRadius,
side: const BorderSide(color: AppColors.darkOutline, width: 1),
),
labelStyle: AppTypography.badgeLabel.copyWith(color: AppColors.darkOnSurface),
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs),
),
dividerTheme: const DividerThemeData(
color: AppColors.darkOutline,
thickness: 1,
space: 1,
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.darkSurface,
indicatorColor: AppColors.darkPrimary.withValues(alpha: 0.14),
elevation: 0,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppTypography.telemetryLabel.copyWith(color: AppColors.darkPrimary);
}
return AppTypography.telemetryLabel.copyWith(color: AppColors.darkOnSurfaceVariant);
}),
),
navigationRailTheme: NavigationRailThemeData(
backgroundColor: AppColors.darkSurface,
indicatorColor: AppColors.darkPrimary.withValues(alpha: 0.14),
elevation: 0,
selectedLabelTextStyle: AppTypography.telemetryLabel.copyWith(color: AppColors.darkPrimary),
unselectedLabelTextStyle: AppTypography.telemetryLabel.copyWith(color: AppColors.darkOnSurfaceVariant),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.darkSurfaceContainerHighest,
contentTextStyle: AppTypography.bodyMd.copyWith(color: AppColors.darkOnSurface),
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorderRadius,
side: const BorderSide(color: AppColors.darkOutlineVariant, width: 0.8),
),
behavior: SnackBarBehavior.floating,
),
);
}
/// Light Theme (Daylight HUD).
static ThemeData get lightTheme {
const colorScheme = ColorScheme.light(
primary: AppColors.lightPrimary,
onPrimary: AppColors.onBrand,
primaryContainer: AppColors.brandContainerLight,
onPrimaryContainer: AppColors.onBrandContainerLight,
secondary: AppColors.lightSecondary,
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFFFDE8E8),
onSecondaryContainer: Color(0xFF410002),
surface: AppColors.lightSurface,
onSurface: AppColors.lightOnSurface,
onSurfaceVariant: AppColors.lightOnSurfaceVariant,
outline: AppColors.lightOutline,
outlineVariant: AppColors.lightOutlineVariant,
error: AppColors.semanticDanger,
onError: Color(0xFFFFFFFF),
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: colorScheme,
scaffoldBackgroundColor: AppColors.lightBackground,
fontFamily: AppTypography.fontBody,
extensions: const [
MotorSignalThemeColors.light,
],
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
scrolledUnderElevation: 0,
titleTextStyle: AppTypography.headlineSm,
iconTheme: IconThemeData(color: AppColors.lightOnSurface),
),
cardTheme: CardThemeData(
color: AppColors.lightSurfaceContainer,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.lgBorderRadius,
side: const BorderSide(color: AppColors.lightOutlineVariant, width: 0.8),
),
),
dialogTheme: DialogThemeData(
backgroundColor: AppColors.lightSurfaceContainer,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.lgBorderRadius,
side: const BorderSide(color: AppColors.lightOutlineVariant, width: 0.8),
),
titleTextStyle: AppTypography.headlineMd.copyWith(color: AppColors.lightOnSurface),
contentTextStyle: AppTypography.bodyMd.copyWith(color: AppColors.lightOnSurfaceVariant),
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: AppColors.lightSurfaceContainer,
modalBackgroundColor: AppColors.lightSurfaceContainer,
elevation: 12,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(AppRadius.xl)),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
visualDensity: VisualDensity.compact,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: WidgetStateProperty.all(
RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
),
side: WidgetStateProperty.resolveWith<BorderSide>((states) {
if (states.contains(WidgetState.selected)) {
return const BorderSide(color: AppColors.lightPrimary, width: 1.2);
}
return const BorderSide(color: AppColors.lightOutlineVariant, width: 0.8);
}),
backgroundColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.lightPrimary.withValues(alpha: 0.10);
}
return AppColors.lightSurfaceContainer;
}),
foregroundColor: WidgetStateProperty.resolveWith<Color?>((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.lightPrimary;
}
return AppColors.lightOnSurfaceVariant;
}),
textStyle: WidgetStateProperty.all(AppTypography.button),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: AppColors.lightPrimary,
foregroundColor: const Color(0xFFFFFFFF),
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.lightSurfaceContainer,
foregroundColor: AppColors.lightOnSurface,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
elevation: 1,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.lightPrimary,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
side: const BorderSide(color: AppColors.lightOutline, width: 1),
shape: RoundedRectangleBorder(borderRadius: AppRadius.mdBorderRadius),
textStyle: AppTypography.button,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.lightPrimary,
minimumSize: const Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget),
textStyle: AppTypography.button,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.lightSurfaceContainer,
contentPadding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
border: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.lightOutline, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.lightOutline, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.lightPrimary, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: AppRadius.mdBorderRadius,
borderSide: const BorderSide(color: AppColors.hazardRed, width: 1.5),
),
labelStyle: AppTypography.bodyMd.copyWith(color: AppColors.lightOnSurfaceVariant),
hintStyle: AppTypography.bodyMd.copyWith(color: AppColors.lightOnSurfaceVariant.withValues(alpha: 0.6)),
),
chipTheme: ChipThemeData(
backgroundColor: AppColors.lightSurfaceContainerHigh,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorderRadius,
side: const BorderSide(color: AppColors.lightOutline, width: 1),
),
labelStyle: AppTypography.badgeLabel.copyWith(color: AppColors.lightOnSurface),
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.sm, vertical: AppSpacing.xs),
),
dividerTheme: const DividerThemeData(
color: AppColors.lightOutlineVariant,
thickness: 1,
space: 1,
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: AppColors.lightSurface,
indicatorColor: AppColors.lightPrimary.withValues(alpha: 0.12),
elevation: 0,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppTypography.telemetryLabel.copyWith(color: AppColors.lightPrimary);
}
return AppTypography.telemetryLabel.copyWith(color: AppColors.lightOnSurfaceVariant);
}),
),
navigationRailTheme: NavigationRailThemeData(
backgroundColor: AppColors.lightSurface,
indicatorColor: AppColors.lightPrimary.withValues(alpha: 0.12),
elevation: 0,
selectedLabelTextStyle: AppTypography.telemetryLabel.copyWith(color: AppColors.lightPrimary),
unselectedLabelTextStyle: AppTypography.telemetryLabel.copyWith(color: AppColors.lightOnSurfaceVariant),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.lightSurfaceContainerHighest,
contentTextStyle: AppTypography.bodyMd.copyWith(color: AppColors.lightOnSurface),
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorderRadius,
side: const BorderSide(color: AppColors.lightOutline, width: 1),
),
behavior: SnackBarBehavior.floating,
),
);
}
}