import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'app_typography.dart'; import 'app_surfaces.dart'; /// M3 Expressive theme for TasQ. /// /// Key differences from the previous Hybrid M2/M3 theme: /// * Cards use **tonal elevation** (surfaceTint color overlays) instead of /// drop-shadows, giving surfaces an organic, seed-tinted look. /// * Large containers use the M3 standard **28 dp** corner radius. /// * Buttons follow the M3 hierarchy: FilledButton (primary), Tonal, Elevated, /// Outlined, and Text. /// * NavigationBar / NavigationRail use pill-shaped indicators with the /// secondary-container tonal color. /// * Spring-physics inspired durations: transitions default to 400 ms with an /// emphasized easing curve. class AppTheme { /// The seed color drives M3's entire tonal palette generation. static const Color _seed = Color(0xFF4A6FA5); // ──────────────────────────────────────────────────────────── // LIGHT // ──────────────────────────────────────────────────────────── static ThemeData light() { final base = ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: _seed, brightness: Brightness.light, ), useMaterial3: true, ); return _apply(base); } // ──────────────────────────────────────────────────────────── // DARK // ──────────────────────────────────────────────────────────── static ThemeData dark() { final base = ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: _seed, brightness: Brightness.dark, ), useMaterial3: true, ); return _apply(base); } // ──────────────────────────────────────────────────────────── // SHARED BUILDER // ──────────────────────────────────────────────────────────── static ThemeData _apply(ThemeData base) { final cs = base.colorScheme; final isDark = cs.brightness == Brightness.dark; final textTheme = GoogleFonts.spaceGroteskTextTheme(base.textTheme); final monoTheme = GoogleFonts.robotoMonoTextTheme(base.textTheme); final mono = AppMonoText( label: monoTheme.labelMedium?.copyWith(letterSpacing: 0.3) ?? const TextStyle(letterSpacing: 0.3), body: monoTheme.bodyMedium?.copyWith(letterSpacing: 0.2) ?? const TextStyle(letterSpacing: 0.2), ); const surfaces = AppSurfaces( cardRadius: 16, compactCardRadius: 12, containerRadius: 28, dialogRadius: 28, chipRadius: 12, ); return base.copyWith( textTheme: textTheme, scaffoldBackgroundColor: isDark ? cs.surface : cs.surfaceContainerLowest, extensions: [mono, surfaces], // ── AppBar ────────────────────────────────────────────── appBarTheme: AppBarTheme( backgroundColor: cs.surface, foregroundColor: cs.onSurface, elevation: 0, scrolledUnderElevation: 2, surfaceTintColor: cs.surfaceTint, centerTitle: false, titleTextStyle: textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, letterSpacing: 0.2, ), ), // ── Cards — M3 Elevated (tonal surface tint, no hard shadow) ── cardTheme: CardThemeData( color: isDark ? cs.surfaceContainer : cs.surfaceContainerLow, elevation: 1, margin: EdgeInsets.zero, shadowColor: Colors.transparent, surfaceTintColor: cs.surfaceTint, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), // ── Chips ─────────────────────────────────────────────── chipTheme: ChipThemeData( backgroundColor: cs.surfaceContainerHighest, side: BorderSide.none, labelStyle: textTheme.labelSmall, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), // ── Dividers ──────────────────────────────────────────── dividerTheme: DividerThemeData(color: cs.outlineVariant, thickness: 1), // ── Input Fields ──────────────────────────────────────── inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: cs.surfaceContainerLow, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: cs.outlineVariant), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: cs.outlineVariant), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: cs.primary, width: 2), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), // ── Buttons — M3 Expressive hierarchy ─────────────────── filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), textStyle: textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: cs.surfaceContainerLow, foregroundColor: cs.primary, elevation: 1, shadowColor: Colors.transparent, surfaceTintColor: cs.surfaceTint, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), textStyle: textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), side: BorderSide(color: cs.outline), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), textStyle: textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), textStyle: textTheme.labelLarge?.copyWith( fontWeight: FontWeight.w600, ), ), ), iconButtonTheme: IconButtonThemeData( style: IconButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( shape: WidgetStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), ), ), // ── FAB — M3 Expressive ────────────────────────────────── floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: cs.primaryContainer, foregroundColor: cs.onPrimaryContainer, elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), // ── Navigation — M3 Expressive pill indicators ────────── navigationDrawerTheme: NavigationDrawerThemeData( backgroundColor: cs.surface, indicatorColor: cs.secondaryContainer, tileHeight: 56, indicatorShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(28), ), ), navigationRailTheme: NavigationRailThemeData( backgroundColor: cs.surface, selectedIconTheme: IconThemeData(color: cs.onSecondaryContainer), unselectedIconTheme: IconThemeData(color: cs.onSurfaceVariant), selectedLabelTextStyle: textTheme.labelMedium?.copyWith( fontWeight: FontWeight.w700, color: cs.onSurface, ), unselectedLabelTextStyle: textTheme.labelMedium?.copyWith( color: cs.onSurfaceVariant, ), indicatorColor: cs.secondaryContainer, indicatorShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(28), ), ), navigationBarTheme: NavigationBarThemeData( backgroundColor: cs.surfaceContainer, indicatorColor: cs.secondaryContainer, indicatorShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), labelTextStyle: WidgetStateProperty.resolveWith((states) { final selected = states.contains(WidgetState.selected); return textTheme.labelMedium?.copyWith( fontWeight: selected ? FontWeight.w700 : FontWeight.w500, color: selected ? cs.onSurface : cs.onSurfaceVariant, ); }), elevation: 2, surfaceTintColor: cs.surfaceTint, ), // ── List Tiles ────────────────────────────────────────── listTileTheme: ListTileThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), tileColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), ), // ── Dialogs ───────────────────────────────────────────── dialogTheme: DialogThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), surfaceTintColor: cs.surfaceTint, backgroundColor: isDark ? cs.surfaceContainerHigh : cs.surfaceContainerLowest, ), // ── Bottom Sheets ─────────────────────────────────────── bottomSheetTheme: BottomSheetThemeData( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(28)), ), backgroundColor: isDark ? cs.surfaceContainerHigh : cs.surfaceContainerLowest, surfaceTintColor: cs.surfaceTint, showDragHandle: true, ), // ── Snackbar ──────────────────────────────────────────── snackBarTheme: SnackBarThemeData( behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), // ── Search Bar ────────────────────────────────────────── searchBarTheme: SearchBarThemeData( elevation: WidgetStateProperty.all(0), backgroundColor: WidgetStateProperty.all(cs.surfaceContainerHigh), shape: WidgetStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)), ), ), // ── Tooltips ──────────────────────────────────────────── tooltipTheme: TooltipThemeData( decoration: BoxDecoration( color: cs.inverseSurface, borderRadius: BorderRadius.circular(8), ), textStyle: textTheme.bodySmall?.copyWith(color: cs.onInverseSurface), ), // ── Tab Bar ───────────────────────────────────────────── tabBarTheme: TabBarThemeData( labelStyle: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w700), unselectedLabelStyle: textTheme.labelLarge, indicatorColor: cs.primary, labelColor: cs.primary, unselectedLabelColor: cs.onSurfaceVariant, indicatorSize: TabBarIndicatorSize.label, dividerColor: cs.outlineVariant, ), // ── PopupMenu ─────────────────────────────────────────── popupMenuTheme: PopupMenuThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), surfaceTintColor: cs.surfaceTint, color: isDark ? cs.surfaceContainerHigh : cs.surfaceContainerLowest, ), ); } }