import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'app_colors.dart'; import 'app_theme.dart'; /// 적응형 테마 관리 클래스 class AdaptiveTheme { /// 라이트 테마 static ThemeData get lightTheme => AppTheme.lightTheme; /// 다크 테마 static ThemeData get darkTheme { const scheme = ColorScheme.dark( primary: AppColors.primaryColor, onPrimary: Colors.white, secondary: AppColors.secondaryColor, tertiary: AppColors.infoColor, error: AppColors.errorColor, surface: Color(0xFF1E1E1E), ); return ThemeData( useMaterial3: true, brightness: Brightness.dark, colorScheme: scheme, scaffoldBackgroundColor: const Color(0xFF121212), cardTheme: CardThemeData( color: scheme.surface, elevation: 1, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( color: const Color(0xFFFFFFFF).withValues(alpha: 0.08), width: 1, ), ), clipBehavior: Clip.antiAlias, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), ), appBarTheme: AppBarTheme( backgroundColor: scheme.surface, foregroundColor: scheme.onSurface, elevation: 0, centerTitle: false, // title/icon colors inherit from foregroundColor ), textTheme: ThemeData.dark(useMaterial3: true) .textTheme .copyWith( headlineLarge: const TextStyle( fontSize: 32, fontWeight: FontWeight.w700, letterSpacing: -0.5, height: 1.2, ), headlineMedium: const TextStyle( fontSize: 28, fontWeight: FontWeight.w700, letterSpacing: -0.5, height: 1.2, ), headlineSmall: const TextStyle( fontSize: 24, fontWeight: FontWeight.w600, letterSpacing: -0.25, height: 1.3, ), titleLarge: const TextStyle( fontSize: 20, fontWeight: FontWeight.w600, letterSpacing: -0.2, height: 1.4, ), titleMedium: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, letterSpacing: -0.1, height: 1.4, ), titleSmall: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, height: 1.4, ), bodyLarge: const TextStyle( fontSize: 16, fontWeight: FontWeight.w400, letterSpacing: 0.1, height: 1.5, ), bodyMedium: const TextStyle( fontSize: 14, fontWeight: FontWeight.w400, letterSpacing: 0.1, height: 1.5, ), bodySmall: const TextStyle( fontSize: 12, fontWeight: FontWeight.w400, letterSpacing: 0.2, height: 1.5, ), labelLarge: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.1, height: 1.4, ), labelMedium: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, letterSpacing: 0.2, height: 1.4, ), labelSmall: const TextStyle( fontSize: 11, fontWeight: FontWeight.w500, letterSpacing: 0.2, height: 1.4, ), ) .apply(bodyColor: scheme.onSurface, displayColor: scheme.onSurface), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: scheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: scheme.outline, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: scheme.primary, width: 1.5), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: scheme.error, width: 1), ), labelStyle: TextStyle( color: scheme.onSurfaceVariant, fontSize: 14, fontWeight: FontWeight.w500, ), hintStyle: TextStyle( color: scheme.onSurfaceVariant, fontSize: 14, fontWeight: FontWeight.w400, ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: scheme.primary, foregroundColor: scheme.onPrimary, minimumSize: const Size(0, 48), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), ), switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return scheme.primary; } return scheme.onSurfaceVariant; }), trackColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return scheme.primary.withValues(alpha: 0.5); } return scheme.surfaceContainerHighest.withValues(alpha: 0.5); }), ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return scheme.primary; } return Colors.transparent; }), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), side: BorderSide(color: scheme.outline, width: 1.5), ), radioTheme: RadioThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return scheme.primary; } return scheme.onSurfaceVariant; }), ), sliderTheme: SliderThemeData( activeTrackColor: scheme.primary, inactiveTrackColor: scheme.onSurfaceVariant, thumbColor: scheme.primary, overlayColor: scheme.primary.withValues(alpha: 0.5), trackHeight: 4, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 10), overlayShape: const RoundSliderOverlayShape(overlayRadius: 20), ), tabBarTheme: TabBarThemeData( labelColor: scheme.primary, unselectedLabelColor: scheme.onSurfaceVariant, indicatorColor: scheme.primary, labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.1, ), unselectedLabelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, letterSpacing: 0.1, ), ), dividerTheme: DividerThemeData( color: scheme.outline, thickness: 1, space: 16, ), ); } /// OLED 최적화 다크 테마 static ThemeData get oledTheme { final base = darkTheme; const oledSurface = Color(0xFF0A0A0A); return base.copyWith( scaffoldBackgroundColor: Colors.black, colorScheme: base.colorScheme.copyWith(surface: oledSurface), cardTheme: base.cardTheme.copyWith(color: oledSurface), appBarTheme: base.appBarTheme.copyWith(backgroundColor: Colors.black), inputDecorationTheme: base.inputDecorationTheme.copyWith( fillColor: oledSurface, ), ); } /// 고대비 테마 static ThemeData get highContrastTheme { return ThemeData( useMaterial3: true, brightness: Brightness.light, colorScheme: const ColorScheme.highContrastLight( primary: Colors.black, secondary: Colors.black87, tertiary: Colors.black54, error: Colors.red, surface: Colors.white, ), textTheme: const TextTheme( headlineLarge: TextStyle( color: Colors.black, fontSize: 32, fontWeight: FontWeight.w900, ), headlineMedium: TextStyle( color: Colors.black, fontSize: 28, fontWeight: FontWeight.w900, ), headlineSmall: TextStyle( color: Colors.black, fontSize: 24, fontWeight: FontWeight.w800, ), bodyLarge: TextStyle( color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600, ), bodyMedium: TextStyle( color: Colors.black87, fontSize: 14, fontWeight: FontWeight.w500, ), ), cardTheme: CardThemeData( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: const BorderSide(color: Colors.black, width: 2), ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, side: const BorderSide(color: Colors.black, width: 2), textStyle: const TextStyle( fontWeight: FontWeight.w700, ), ), ), ); } /// 시스템 테마에 따른 상태바 스타일 적용 /// Android 15+ edge-to-edge 호환: deprecated된 네비게이션바 색상 API 제거 static void applySystemUIOverlay(BuildContext context) { final brightness = Theme.of(context).brightness; SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: brightness == Brightness.dark ? Brightness.light : Brightness.dark, statusBarBrightness: brightness == Brightness.dark ? Brightness.light : Brightness.dark, // Android 15+: 네비게이션바 색상은 시스템이 자동 처리 systemNavigationBarContrastEnforced: false, )); } /// 접근성 설정에 따른 테마 조정 static ThemeData getAccessibleTheme( ThemeData baseTheme, { required bool largeText, required bool reduceMotion, required bool highContrast, }) { if (highContrast) { return highContrastTheme; } ThemeData theme = baseTheme; if (largeText) { theme = theme.copyWith( textTheme: theme.textTheme.apply( fontSizeFactor: 1.2, ), ); } if (reduceMotion) { theme = theme.copyWith( pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(), TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(), }, ), ); } return theme; } } /// 테마 모드 열거형 enum AppThemeMode { light, dark, oled, system, } /// 테마 설정 클래스 class ThemeSettings { final AppThemeMode mode; final bool useSystemColors; final bool largeText; final bool reduceMotion; final bool highContrast; const ThemeSettings({ this.mode = AppThemeMode.system, this.useSystemColors = false, this.largeText = false, this.reduceMotion = false, this.highContrast = false, }); ThemeSettings copyWith({ AppThemeMode? mode, bool? useSystemColors, bool? largeText, bool? reduceMotion, bool? highContrast, }) { return ThemeSettings( mode: mode ?? this.mode, useSystemColors: useSystemColors ?? this.useSystemColors, largeText: largeText ?? this.largeText, reduceMotion: reduceMotion ?? this.reduceMotion, highContrast: highContrast ?? this.highContrast, ); } Map toJson() => { 'mode': mode.name, 'useSystemColors': useSystemColors, 'largeText': largeText, 'reduceMotion': reduceMotion, 'highContrast': highContrast, }; factory ThemeSettings.fromJson(Map json) { return ThemeSettings( mode: AppThemeMode.values.firstWhere( (mode) => mode.name == json['mode'], orElse: () => AppThemeMode.system, ), useSystemColors: json['useSystemColors'] ?? false, largeText: json['largeText'] ?? false, reduceMotion: json['reduceMotion'] ?? false, highContrast: json['highContrast'] ?? false, ); } }