import 'dart:io'; import 'dart:core'; import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/yamltheme.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/settings.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as path; const custom_themes_subdir = "themes"; const mode_light = "light"; const mode_dark = "dark"; final TextStyle defaultSmallTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.normal, fontSize: 10); final TextStyle defaultMessageTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w400, fontSize: 13, fontFamilyFallback: [Platform.isWindows ? 'Segoe UI Emoji' : "Noto Color Emoji"]); final TextStyle defaultFormLabelTextStyle = TextStyle( fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 20, ); final TextStyle defaultTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12); final TextStyle defaultTextButtonStyle = defaultTextStyle.copyWith(fontWeight: FontWeight.bold); final TextStyle defaultDropDownMenuItemTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 16); class ThemeLoader extends ChangeNotifier { Map> themes = Map(); LoadThemes(String cwtchDir) async { themes.clear(); // clear themes... loadBuiltinThemes().then((builtinThemes) { themes.addAll(builtinThemes); notifyListeners(); loadCustomThemes(path.join(cwtchDir, custom_themes_subdir)).then((customThemes) { themes.addAll(customThemes); notifyListeners(); }); }); } OpaqueThemeType getTheme(String? themeId, String? mode) { if (themeId == null) { themeId = cwtch_theme; } if (themeId == mode_light) { mode = mode_light; } if (themeId == mode_dark) { mode = mode_dark; } var theme = themes[themeId]?[mode] ?? themes[themeId]?[flipMode(mode ?? mode_dark)]; return theme ?? CwtchDark(); } String flipMode(String mode) { if (mode == mode_dark) { return mode_light; } return mode_dark; } } Color lighten(Color color, [double amount = 0.15]) { final hsl = HSLColor.fromColor(color); final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0)); return hslLight.toColor(); } Color darken(Color color, [double amount = 0.15]) { final hsl = HSLColor.fromColor(color); final hslDarken = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); return hslDarken.toColor(); } abstract class OpaqueThemeType { static final Color red = Color(0xFFFF0000); get theme => "dummy"; get mode => mode_light; // Main screen background color (message pane, item rows) get backgroundMainColor => red; // pane colors (settings) get backgroundPaneColor => red; get topbarColor => red; get mainTextColor => red; // pressed row, offline heart get hilightElementColor => red; // Selected Row get backgroundHilightElementColor => red; // Faded text color for suggestions in textfields // Todo: implement way more places get sendHintTextColor => red; get defaultButtonColor => red; get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(defaultButtonColor); get defaultButtonTextColor => red; get defaultButtonDisabledColor => red; get textfieldBackgroundColor => red; get textfieldBorderColor => red; get textfieldHintColor => red; get textfieldErrorColor => red; get textfieldSelectionColor => red; get scrollbarDefaultColor => red; get portraitBackgroundColor => red; get portraitOnlineBorderColor => red; get portraitOfflineBorderColor => red; get portraitBlockedBorderColor => red; get portraitBlockedTextColor => red; get portraitContactBadgeColor => red; get portraitContactBadgeTextColor => red; get portraitProfileBadgeColor => red; get portraitProfileBadgeTextColor => red; get portraitOnlineAwayColor => Color(0xFFFFF59D); get portraitOnlineBusyColor => Color(0xFFEF9A9A); // dropshaddpow // todo: probably should not be reply icon color in messagerow get dropShadowColor => red; get toolbarIconColor => red; get chatReactionIconColor => red; get messageFromMeBackgroundColor => red; get messageFromMeTextColor => red; get messageFromOtherBackgroundColor => red; get messageFromOtherTextColor => red; get messageSelectionColor => red; get menuBackgroundColor => red; get snackbarBackgroundColor => red; get snackbarTextColor => red; // Images get chatImageColor => red; get chatImage => null; ImageProvider loadImage(String key, {BuildContext? context}) { return AssetImage(""); } // Sizes double contactOnionTextSize() { return 18; } } // Borrowed from Stackoverflow MaterialColor getMaterialColor(Color color) { final int red = color.red; final int green = color.green; final int blue = color.blue; final Map shades = { 50: Color.fromRGBO(red, green, blue, .1), 100: Color.fromRGBO(red, green, blue, .2), 200: Color.fromRGBO(red, green, blue, .3), 300: Color.fromRGBO(red, green, blue, .4), 400: Color.fromRGBO(red, green, blue, .5), 500: Color.fromRGBO(red, green, blue, .6), 600: Color.fromRGBO(red, green, blue, .7), 700: Color.fromRGBO(red, green, blue, .8), 800: Color.fromRGBO(red, green, blue, .9), 900: Color.fromRGBO(red, green, blue, 1), }; return MaterialColor(color.value, shades); } ThemeData mkThemeData(Settings opaque) { return ThemeData( hoverColor: opaque.current().backgroundHilightElementColor.withOpacity(0.5), visualDensity: VisualDensity.adaptivePlatformDensity, primaryIconTheme: IconThemeData( color: opaque.current().mainTextColor, ), primaryColor: opaque.current().mainTextColor, canvasColor: opaque.current().backgroundMainColor, highlightColor: opaque.current().hilightElementColor, iconTheme: IconThemeData( color: opaque.current().toolbarIconColor, ), cardColor: opaque.current().backgroundMainColor, bottomSheetTheme: BottomSheetThemeData( backgroundColor: opaque.current().backgroundPaneColor, constraints: const BoxConstraints( maxWidth: double.infinity, ), ), appBarTheme: AppBarTheme( systemOverlayStyle: SystemUiOverlayStyle( // Status bar color statusBarColor: opaque.current().topbarColor, // Status bar brightness (optional) statusBarIconBrightness: opaque.current().mode == mode_light ? Brightness.dark : Brightness.light, // For Android (dark icons) statusBarBrightness: opaque.current().mode == mode_light ? Brightness.dark : Brightness.light, // For iOS (dark icons) ), backgroundColor: opaque.current().topbarColor, iconTheme: IconThemeData( color: opaque.current().mainTextColor, ), titleTextStyle: TextStyle(fontWeight: FontWeight.bold, fontFamily: "Inter", color: opaque.current().mainTextColor, fontSize: opaque.fontScaling * 18.0), actionsIconTheme: IconThemeData( color: opaque.current().mainTextColor, )), listTileTheme: ListTileThemeData( titleTextStyle: defaultFormLabelTextStyle.copyWith(color: opaque.current().mainTextColor), subtitleTextStyle: defaultMessageTextStyle.copyWith(color: opaque.current().mainTextColor)), iconButtonTheme: IconButtonThemeData(style: ButtonStyle(textStyle: MaterialStateProperty.all(defaultFormLabelTextStyle))), //bottomNavigationBarTheme: BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed, backgroundColor: opaque.current().backgroundHilightElementColor), // Can't determine current use textButtonTheme: TextButtonThemeData( style: ButtonStyle( backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor), foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor), overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor), padding: MaterialStateProperty.all(EdgeInsets.all(20))), ), hintColor: opaque.current().textfieldHintColor, elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( backgroundColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor : opaque.current().defaultButtonColor), foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor), overlayColor: MaterialStateProperty.resolveWith((states) => (states.contains(MaterialState.pressed) && states.contains(MaterialState.hovered)) ? opaque.current().defaultButtonActiveColor : states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor : null), enableFeedback: true, textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)), padding: MaterialStateProperty.all(EdgeInsets.all(20)), shape: MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(6.0), )), ), ), scrollbarTheme: ScrollbarThemeData(thumbVisibility: MaterialStateProperty.all(false), thumbColor: MaterialStateProperty.all(opaque.current().scrollbarDefaultColor)), tabBarTheme: TabBarTheme( labelColor: opaque.current().mainTextColor, unselectedLabelColor: opaque.current().mainTextColor, indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor)), labelStyle: opaque.scaleFonts(defaultTextButtonStyle).copyWith(color: opaque.current().mainTextColor), unselectedLabelStyle: opaque.scaleFonts(defaultTextStyle).copyWith(color: opaque.current().mainTextColor), tabAlignment: TabAlignment.center), dialogTheme: DialogTheme( backgroundColor: opaque.current().backgroundPaneColor, titleTextStyle: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, color: opaque.current().mainTextColor), contentTextStyle: TextStyle( fontFamily: "Inter", color: opaque.current().mainTextColor, )), textTheme: TextTheme( // NOTE: The following font scales were arrived at after consulting the material text scale // docs: https://m3.material.io/styles/typography/type-scale-tokens and some trial and error displaySmall: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor), displayMedium: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor), displayLarge: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor), titleSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor), titleLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor), titleMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 20.0, color: opaque.current().mainTextColor), bodySmall: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 12.0, color: opaque.current().mainTextColor), bodyMedium: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor), bodyLarge: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor), headlineSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 24.0, color: opaque.current().mainTextColor), headlineMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 26.0, color: opaque.current().mainTextColor), headlineLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 28.0, color: opaque.current().mainTextColor), labelSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w100, fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor), labelMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w300, fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor), labelLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w200, fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor), ), switchTheme: SwitchThemeData( overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor), thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor), trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor), ), // the only way to change the text Selection Context Menu Color ?! brightness: opaque.current().mode == mode_dark ? Brightness.dark : Brightness.light, floatingActionButtonTheme: FloatingActionButtonThemeData( foregroundColor: opaque.current().mainTextColor, backgroundColor: opaque.current().defaultButtonColor, hoverColor: opaque.current().defaultButtonActiveColor, enableFeedback: true, splashColor: opaque.current().defaultButtonActiveColor), textSelectionTheme: TextSelectionThemeData( cursorColor: opaque.current().textfieldSelectionColor, selectionColor: opaque.current().textfieldSelectionColor, selectionHandleColor: opaque.current().textfieldSelectionColor), popupMenuTheme: PopupMenuThemeData( color: opaque.current().backgroundPaneColor.withOpacity(0.9), ), snackBarTheme: SnackBarThemeData( backgroundColor: opaque.current().snackbarBackgroundColor, contentTextStyle: TextStyle(color: opaque.current().snackbarTextColor), )); }