import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/themes/color_provider.dart'; import 'package:smooth_app/themes/color_schemes.dart'; import 'package:smooth_app/themes/contrast_provider.dart'; import 'package:smooth_app/themes/smooth_theme_colors.dart'; import 'package:smooth_app/themes/theme_provider.dart'; class SmoothTheme { const SmoothTheme._(); static const double ADDITIONAL_OPACITY_FOR_DARK = .3; static const double SECONDARY_COLOR_SHADE_VALUE = 0.4; static ThemeData getThemeData( final Brightness brightness, final ThemeProvider themeProvider, final ColorProvider Function() colorProvider, final TextContrastProvider Function() textContrastProvider, ) { final bool lightTheme = brightness == Brightness.light; ColorScheme myColorScheme; if (lightTheme) { myColorScheme = lightColorScheme; } else { if (themeProvider.currentTheme == THEME_AMOLED) { final ColorProvider colorNotifier = colorProvider(); myColorScheme = trueDarkColorScheme.copyWith( primary: getColorValue(colorNotifier.currentColor), secondary: getShade( getColorValue(colorNotifier.currentColor), darker: true, value: SECONDARY_COLOR_SHADE_VALUE, ), ); } else { myColorScheme = darkColorScheme; } } final SmoothColorsThemeExtension smoothExtension = SmoothColorsThemeExtension.defaultValues(lightTheme); final TextTheme textTheme = brightness == Brightness.dark ? getTextTheme(themeProvider, textContrastProvider) : _TEXT_THEME; return ThemeData( fontFamily: 'OpenSans', primaryColor: DARK_BROWN_COLOR, extensions: >[smoothExtension], colorScheme: myColorScheme, canvasColor: themeProvider.currentTheme == THEME_AMOLED ? myColorScheme.surface : null, scaffoldBackgroundColor: lightTheme ? null : const Color(0xFF303030), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith( (Set states) => states.contains(WidgetState.disabled) ? Colors.grey : myColorScheme.primary, ), foregroundColor: WidgetStateProperty.resolveWith( (Set states) => states.contains(WidgetState.disabled) ? Colors.white : myColorScheme.onPrimary, ), iconColor: WidgetStateProperty.all(myColorScheme.onPrimary), ), ), floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: lightTheme ? smoothExtension.primaryDark : myColorScheme.primary, foregroundColor: myColorScheme.onPrimary, ), textTheme: textTheme, appBarTheme: AppBarTheme( centerTitle: false, color: myColorScheme.surface, foregroundColor: myColorScheme.onSurface, systemOverlayStyle: SystemUiOverlayStyle.light, titleTextStyle: textTheme.titleLarge, ), dividerTheme: const DividerThemeData( color: Color(0xFFECECEC), space: 1.0, ), dividerColor: const Color(0xFFDFDFDF), inputDecorationTheme: InputDecorationTheme( fillColor: myColorScheme.secondary, ), iconTheme: IconThemeData(color: myColorScheme.onSurface), snackBarTheme: SnackBarThemeData( contentTextStyle: _TEXT_THEME.bodyMedium?.copyWith( color: Colors.white, fontWeight: FontWeight.w500, ), actionTextColor: Colors.white, actionBackgroundColor: smoothExtension.primaryDark, backgroundColor: smoothExtension.primaryBlack, ), bannerTheme: MaterialBannerThemeData( contentTextStyle: TextStyle(color: myColorScheme.onSecondary), backgroundColor: myColorScheme.secondary, ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith(( Set states, ) { if (states.contains(WidgetState.disabled)) { return null; } if (states.contains(WidgetState.selected)) { return lightTheme ? smoothExtension.primarySemiDark : smoothExtension.primaryNormal; } return null; }), side: BorderSide( color: lightTheme ? smoothExtension.primaryBlack : smoothExtension.primarySemiDark, width: 2.0, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0)), checkColor: const WidgetStatePropertyAll(Colors.white), ), radioTheme: RadioThemeData( fillColor: WidgetStateProperty.resolveWith(( Set states, ) { if (states.contains(WidgetState.disabled)) { return null; } if (states.contains(WidgetState.selected)) { return myColorScheme.primary; } return null; }), ), switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith(( Set states, ) { if (states.contains(WidgetState.selected)) { if (lightTheme) { return smoothExtension.primaryDark; } else { return smoothExtension.primarySemiDark; } } else if (states.contains(WidgetState.disabled)) { if (lightTheme) { return const Color(0xFFC2B5B0); } else { return smoothExtension.primaryNormal; } } else { return null; } }), trackColor: WidgetStateProperty.resolveWith(( Set states, ) { if (lightTheme) { return smoothExtension.primaryMedium; } else { return const Color(0xFFEDE0DB); } }), ), ); } static TextTheme getTextTheme( ThemeProvider themeProvider, TextContrastProvider Function() textContrastProvider, ) { final Color contrastLevel = themeProvider.currentTheme == THEME_AMOLED ? getTextContrastLevel(textContrastProvider().currentContrastLevel) : Colors.white; return _TEXT_THEME.copyWith( displayMedium: _TEXT_THEME.displayMedium?.copyWith(color: contrastLevel), headlineMedium: _TEXT_THEME.headlineMedium?.copyWith( color: contrastLevel, ), bodyMedium: _TEXT_THEME.bodyMedium?.copyWith(color: contrastLevel), displaySmall: _TEXT_THEME.bodySmall?.copyWith(color: contrastLevel), titleLarge: _TEXT_THEME.titleLarge?.copyWith(color: contrastLevel), titleMedium: _TEXT_THEME.titleMedium?.copyWith(color: contrastLevel), titleSmall: _TEXT_THEME.titleSmall?.copyWith(color: contrastLevel), ); } static const TextTheme _TEXT_THEME = TextTheme( displayLarge: TextStyle(fontSize: 28.0, fontWeight: FontWeight.bold), displayMedium: TextStyle( fontSize: 24.0, fontWeight: FontWeight.bold, color: Colors.black, ), displaySmall: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold), headlineMedium: TextStyle( fontSize: LARGE_SPACE, fontWeight: FontWeight.bold, color: Colors.black, ), bodyMedium: TextStyle(fontSize: 14, letterSpacing: 0.5), titleMedium: TextStyle(fontSize: 14.0), titleSmall: TextStyle(fontSize: 12.0), ); static MaterialColor getMaterialColorFromColor(Color color) { final Map colorShades = { 50: getShade(color, value: 0.5), 100: getShade(color, value: 0.4), 200: getShade(color, value: 0.3), 300: getShade(color, value: 0.2), 400: getShade(color, value: 0.1), 500: color, 600: getShade(color, value: 0.1, darker: true), 700: getShade(color, value: 0.15, darker: true), 800: getShade(color, value: 0.2, darker: true), 900: getShade(color, value: 0.25, darker: true), }; return MaterialColor(color.intValue, colorShades); } //From: https://stackoverflow.com/a/58604669/13313941 static Color getShade(Color color, {bool darker = false, double value = .1}) { assert(value >= 0 && value <= 1); final HSLColor hsl = HSLColor.fromColor(color); final HSLColor hslDark = hsl.withLightness( (darker ? (hsl.lightness - value) : (hsl.lightness + value)).clamp( 0.0, 1.0, ), ); return hslDark.toColor(); } } extension SmoothThemeExtension on BuildContext { T extension() { return Theme.of(this).extension()!; } } extension SmoothColorExtension on Color { /// ignore: deprecated_member_use /// [Color.value] is deprecated, use [Color.intValue] instead int get intValue { final int a = (this.a * 255).round(); final int r = (this.r * 255).round(); final int g = (this.g * 255).round(); final int b = (this.b * 255).round(); // Combine the components into a single int using bit shifting return (a << 24) | (r << 16) | (g << 8) | b; } }