/// Material 3 light and dark themes, including typography and component styles. library; import 'package:flutter/material.dart'; /// Builds the [ThemeData] used by [MaterialApp] for light and dark mode. /// /// Colors are generated from a single seed so both schemes stay consistent /// with Material 3 tonal palettes. abstract final class AppTheme { static const Color seed = Color(0xFF0F766E); static ThemeData light() => _build(Brightness.light); static ThemeData dark() => _build(Brightness.dark); static ThemeData _build(Brightness brightness) { final colorScheme = ColorScheme.fromSeed( seedColor: seed, brightness: brightness, ); final isDark = brightness == Brightness.dark; final textTheme = _textTheme(colorScheme); return ThemeData( useMaterial3: true, colorScheme: colorScheme, textTheme: textTheme, scaffoldBackgroundColor: colorScheme.surface, appBarTheme: AppBarTheme( centerTitle: false, elevation: 0, scrolledUnderElevation: 1, backgroundColor: colorScheme.surface, foregroundColor: colorScheme.onSurface, titleTextStyle: textTheme.titleLarge, ), cardTheme: CardThemeData( elevation: 0, color: colorScheme.surfaceContainerLow, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), clipBehavior: Clip.antiAlias, margin: EdgeInsets.zero, ), navigationBarTheme: NavigationBarThemeData( elevation: 0, height: 72, backgroundColor: colorScheme.surfaceContainer, indicatorColor: colorScheme.secondaryContainer, labelTextStyle: WidgetStateProperty.resolveWith((states) { final selected = states.contains(WidgetState.selected); return textTheme.labelMedium?.copyWith( fontWeight: selected ? FontWeight.w600 : FontWeight.w500, ); }), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: colorScheme.surfaceContainerHighest.withValues(alpha: 0.6), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: colorScheme.outlineVariant), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: colorScheme.primary, width: 2), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( minimumSize: const Size.fromHeight(48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), textStyle: textTheme.labelLarge, ), ), floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), dividerTheme: DividerThemeData( color: colorScheme.outlineVariant, space: 1, ), snackBarTheme: SnackBarThemeData( behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), listTileTheme: ListTileThemeData( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), iconColor: colorScheme.primary, ), shadowColor: isDark ? Colors.black : const Color(0x1A0F172A), ); } /// Slightly tighter, more editorial type scale than the Material defaults. static TextTheme _textTheme(ColorScheme colors) { const family = 'Roboto'; return TextTheme( displaySmall: TextStyle( fontFamily: family, fontSize: 36, fontWeight: FontWeight.w700, letterSpacing: -0.5, color: colors.onSurface, ), headlineMedium: TextStyle( fontFamily: family, fontSize: 28, fontWeight: FontWeight.w600, letterSpacing: -0.3, color: colors.onSurface, ), titleLarge: TextStyle( fontFamily: family, fontSize: 22, fontWeight: FontWeight.w600, color: colors.onSurface, ), titleMedium: TextStyle( fontFamily: family, fontSize: 16, fontWeight: FontWeight.w600, color: colors.onSurface, ), bodyLarge: TextStyle( fontFamily: family, fontSize: 16, height: 1.45, color: colors.onSurface, ), bodyMedium: TextStyle( fontFamily: family, fontSize: 14, height: 1.4, color: colors.onSurfaceVariant, ), labelLarge: TextStyle( fontFamily: family, fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.1, color: colors.onPrimary, ), labelMedium: TextStyle( fontFamily: family, fontSize: 12, fontWeight: FontWeight.w500, letterSpacing: 0.4, color: colors.onSurfaceVariant, ), ); } }