403 lines
17 KiB
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: Color(0xFF000000),
|
|
primaryContainer: Color(0xFF004D56),
|
|
onPrimaryContainer: AppColors.cyberCyan,
|
|
secondary: AppColors.darkSecondary,
|
|
onSecondary: Color(0xFFFFFFFF),
|
|
secondaryContainer: Color(0xFF5A0000),
|
|
onSecondaryContainer: Color(0xFFFFB4AB),
|
|
surface: AppColors.darkSurface,
|
|
onSurface: AppColors.darkOnSurface,
|
|
onSurfaceVariant: AppColors.darkOnSurfaceVariant,
|
|
outline: AppColors.darkOutline,
|
|
outlineVariant: AppColors.darkOutlineVariant,
|
|
error: AppColors.hazardRed,
|
|
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: Color(0xFFFFFFFF),
|
|
primaryContainer: Color(0xFFD6E4FF),
|
|
onPrimaryContainer: Color(0xFF001B3E),
|
|
secondary: AppColors.lightSecondary,
|
|
onSecondary: Color(0xFFFFFFFF),
|
|
secondaryContainer: Color(0xFFFFDAD6),
|
|
onSecondaryContainer: Color(0xFF410002),
|
|
surface: AppColors.lightSurface,
|
|
onSurface: AppColors.lightOnSurface,
|
|
onSurfaceVariant: AppColors.lightOnSurfaceVariant,
|
|
outline: AppColors.lightOutline,
|
|
outlineVariant: AppColors.lightOutlineVariant,
|
|
error: AppColors.hazardRed,
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|