LCOV - code coverage report
Current view: top level - lib/themes - opaque.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 122 212 57.5 %
Date: 2026-07-15 19:28:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:io';
       2             : import 'dart:core';
       3             : 
       4             : import 'package:cwtch/themes/cwtch.dart';
       5             : import 'package:cwtch/themes/yamltheme.dart';
       6             : import 'package:flutter/material.dart';
       7             : import 'package:cwtch/settings.dart';
       8             : import 'package:flutter/services.dart';
       9             : import 'package:path/path.dart' as path;
      10             : 
      11             : const custom_themes_subdir = "themes";
      12             : 
      13             : const mode_light = "light";
      14             : const mode_dark = "dark";
      15             : 
      16           0 : final TextStyle defaultSmallTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.normal, fontSize: 10);
      17          20 : final TextStyle defaultMessageTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w400, fontSize: 13, fontFamilyFallback: [Platform.isWindows ? 'Segoe UI Emoji' : "Noto Color Emoji"]);
      18          12 : final TextStyle defaultFormLabelTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 20);
      19          12 : final TextStyle defaultTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12);
      20          16 : final TextStyle defaultTextButtonStyle = defaultTextStyle.copyWith(fontWeight: FontWeight.bold);
      21          12 : final TextStyle defaultDropDownMenuItemTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 16);
      22             : 
      23             : class ThemeLoader extends ChangeNotifier {
      24             :   Map<String, Map<String, OpaqueThemeType>> themes = Map();
      25             : 
      26           0 :   LoadThemes(String cwtchDir) async {
      27           0 :     themes.clear(); // clear themes...
      28           0 :     loadBuiltinThemes().then((builtinThemes) {
      29           0 :       themes.addAll(builtinThemes);
      30           0 :       notifyListeners();
      31           0 :       loadCustomThemes(path.join(cwtchDir, custom_themes_subdir)).then((customThemes) {
      32           0 :         themes.addAll(customThemes);
      33           0 :         notifyListeners();
      34             :       });
      35             :     });
      36             :   }
      37             : 
      38           4 :   OpaqueThemeType getTheme(String? themeId, String? mode) {
      39             :     if (themeId == null) {
      40             :       themeId = cwtch_theme;
      41             :     }
      42           4 :     if (themeId == mode_light) {
      43             :       mode = mode_light;
      44             :     }
      45           4 :     if (themeId == mode_dark) {
      46             :       mode = mode_dark;
      47             :     }
      48          16 :     var theme = themes[themeId]?[mode] ?? themes[themeId]?[flipMode(mode ?? mode_dark)];
      49           4 :     return theme ?? CwtchDark();
      50             :   }
      51             : 
      52           0 :   String flipMode(String mode) {
      53           0 :     if (mode == mode_dark) {
      54             :       return mode_light;
      55             :     }
      56             :     return mode_dark;
      57             :   }
      58             : }
      59             : 
      60           4 : Color lighten(Color color, [double amount = 0.15]) {
      61           4 :   final hsl = HSLColor.fromColor(color);
      62          16 :   final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
      63             : 
      64           4 :   return hslLight.toColor();
      65             : }
      66             : 
      67           0 : Color darken(Color color, [double amount = 0.15]) {
      68           0 :   final hsl = HSLColor.fromColor(color);
      69           0 :   final hslDarken = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
      70             : 
      71           0 :   return hslDarken.toColor();
      72             : }
      73             : 
      74             : abstract class OpaqueThemeType {
      75           0 :   static final Color red = Color(0xFFFF0000);
      76             : 
      77           0 :   get theme => "dummy";
      78           0 :   get mode => mode_light;
      79             : 
      80             :   // Main screen background color (message pane, item rows)
      81           0 :   get backgroundMainColor => red;
      82             : 
      83             :   // pane colors (settings)
      84           0 :   get backgroundPaneColor => red;
      85             : 
      86           0 :   get topbarColor => red;
      87             : 
      88           0 :   get mainTextColor => red;
      89             : 
      90             :   // pressed row, offline heart
      91           0 :   get hilightElementColor => red;
      92             :   // Selected Row
      93           0 :   get backgroundHilightElementColor => red;
      94             :   // Faded text color for suggestions in textfields
      95             :   // Todo: implement way more places
      96           0 :   get sendHintTextColor => red;
      97             : 
      98           0 :   get defaultButtonColor => red;
      99          12 :   get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(defaultButtonColor);
     100           0 :   get defaultButtonTextColor => red;
     101           0 :   get defaultButtonDisabledColor => red;
     102           0 :   get textfieldBackgroundColor => red;
     103           0 :   get textfieldBorderColor => red;
     104           0 :   get textfieldHintColor => red;
     105           0 :   get textfieldErrorColor => red;
     106           0 :   get textfieldSelectionColor => red;
     107           0 :   get scrollbarDefaultColor => red;
     108           0 :   get portraitBackgroundColor => red;
     109           0 :   get portraitOnlineBorderColor => red;
     110           0 :   get portraitOfflineBorderColor => red;
     111           0 :   get portraitBlockedBorderColor => red;
     112           0 :   get portraitBlockedTextColor => red;
     113           0 :   get portraitContactBadgeColor => red;
     114           0 :   get portraitContactBadgeTextColor => red;
     115           0 :   get portraitProfileBadgeColor => red;
     116           0 :   get portraitProfileBadgeTextColor => red;
     117             : 
     118           0 :   get portraitOnlineAwayColor => Color(0xFFFFF59D);
     119           0 :   get portraitOnlineBusyColor => Color(0xFFEF9A9A);
     120             : 
     121             :   // dropshaddpow
     122             :   // todo: probably should not be reply icon color in messagerow
     123           0 :   get dropShadowColor => red;
     124             : 
     125           0 :   get toolbarIconColor => red;
     126           0 :   get toolbarBackgroundColor => red;
     127           0 :   get chatReactionIconColor => red;
     128           0 :   get messageFromMeBackgroundColor => red;
     129           0 :   get messageFromMeTextColor => red;
     130           0 :   get messageFromOtherBackgroundColor => red;
     131           0 :   get messageFromOtherTextColor => red;
     132           0 :   get messageSelectionColor => red;
     133             : 
     134           0 :   get menuBackgroundColor => red;
     135             : 
     136           0 :   get snackbarBackgroundColor => red;
     137           0 :   get snackbarTextColor => red;
     138             : 
     139             :   // Images
     140             : 
     141           0 :   get chatImageColor => red;
     142           0 :   get chatImage => null;
     143             : 
     144           0 :   ImageProvider loadImage(String key, {BuildContext? context}) {
     145           0 :     return AssetImage("");
     146             :   }
     147             : 
     148             :   // Sizes
     149           0 :   double contactOnionTextSize() {
     150             :     return 18;
     151             :   }
     152             : }
     153             : 
     154             : // Borrowed from Stackoverflow
     155           0 : MaterialColor getMaterialColor(Color color) {
     156           0 :   final int red = color.red;
     157           0 :   final int green = color.green;
     158           0 :   final int blue = color.blue;
     159             : 
     160           0 :   final Map<int, Color> shades = {
     161           0 :     50: Color.fromRGBO(red, green, blue, .1),
     162           0 :     100: Color.fromRGBO(red, green, blue, .2),
     163           0 :     200: Color.fromRGBO(red, green, blue, .3),
     164           0 :     300: Color.fromRGBO(red, green, blue, .4),
     165           0 :     400: Color.fromRGBO(red, green, blue, .5),
     166           0 :     500: Color.fromRGBO(red, green, blue, .6),
     167           0 :     600: Color.fromRGBO(red, green, blue, .7),
     168           0 :     700: Color.fromRGBO(red, green, blue, .8),
     169           0 :     800: Color.fromRGBO(red, green, blue, .9),
     170           0 :     900: Color.fromRGBO(red, green, blue, 1),
     171             :   };
     172             : 
     173           0 :   return MaterialColor(color.value, shades);
     174             : }
     175             : 
     176           4 : ThemeData mkThemeData(Settings opaque) {
     177           4 :   return ThemeData(
     178          12 :     hoverColor: opaque.current().backgroundHilightElementColor.withOpacity(0.5),
     179           4 :     visualDensity: VisualDensity.adaptivePlatformDensity,
     180          12 :     primaryIconTheme: IconThemeData(color: opaque.current().mainTextColor),
     181           8 :     primaryColor: opaque.current().mainTextColor,
     182           8 :     canvasColor: opaque.current().backgroundMainColor,
     183          24 :     highlightColor: opaque.current().mode == mode_light ? darken(opaque.current().backgroundHilightElementColor) : lighten(opaque.current().backgroundHilightElementColor),
     184          12 :     iconTheme: IconThemeData(color: opaque.current().toolbarIconColor),
     185           8 :     cardColor: opaque.current().backgroundMainColor,
     186           4 :     bottomSheetTheme: BottomSheetThemeData(
     187           8 :       backgroundColor: opaque.current().backgroundPaneColor,
     188             :       constraints: const BoxConstraints(maxWidth: double.infinity),
     189             :     ),
     190           4 :     appBarTheme: AppBarTheme(
     191           4 :       systemOverlayStyle: SystemUiOverlayStyle(
     192             :         // Status bar color
     193           8 :         statusBarColor: opaque.current().topbarColor,
     194             :         // Status bar brightness (optional)
     195          12 :         statusBarIconBrightness: opaque.current().mode == mode_light ? Brightness.dark : Brightness.light, // For Android (dark icons)
     196          12 :         statusBarBrightness: opaque.current().mode == mode_light ? Brightness.dark : Brightness.light, // For iOS (dark icons)
     197             :       ),
     198           8 :       backgroundColor: opaque.current().topbarColor,
     199             :       // surfaceTint when not set changes when anything scrolls under it :(
     200           8 :       surfaceTintColor: opaque.current().topbarColor,
     201          12 :       iconTheme: IconThemeData(color: opaque.current().mainTextColor),
     202          20 :       titleTextStyle: TextStyle(fontWeight: FontWeight.bold, fontFamily: "Inter", color: opaque.current().mainTextColor, fontSize: opaque.fontScaling * 18.0),
     203          12 :       actionsIconTheme: IconThemeData(color: opaque.current().mainTextColor),
     204             :     ),
     205           4 :     listTileTheme: ListTileThemeData(
     206          16 :       titleTextStyle: defaultFormLabelTextStyle.copyWith(color: opaque.current().mainTextColor),
     207          16 :       subtitleTextStyle: defaultMessageTextStyle.copyWith(color: opaque.current().mainTextColor),
     208             :     ),
     209             :     //bottomNavigationBarTheme: BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed, backgroundColor: opaque.current().backgroundHilightElementColor),  // Can't determine current use
     210           4 :     textButtonTheme: TextButtonThemeData(
     211           4 :       style: ButtonStyle(
     212          12 :         backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor),
     213          12 :         foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor),
     214          12 :         overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor),
     215           8 :         padding: MaterialStateProperty.all(EdgeInsets.all(20)),
     216             :       ),
     217             :     ),
     218           8 :     hintColor: opaque.current().textfieldHintColor,
     219           4 :     elevatedButtonTheme: ElevatedButtonThemeData(
     220           4 :       style: ButtonStyle(
     221           4 :         backgroundColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor : opaque.current().defaultButtonColor),
     222          12 :         foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor),
     223           4 :         overlayColor: MaterialStateProperty.resolveWith(
     224           0 :           (states) => (states.contains(MaterialState.pressed) && states.contains(MaterialState.hovered))
     225           0 :               ? opaque.current().defaultButtonActiveColor
     226           0 :               : states.contains(MaterialState.disabled)
     227           0 :               ? opaque.current().defaultButtonDisabledColor
     228             :               : null,
     229             :         ),
     230             :         enableFeedback: true,
     231          12 :         textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)),
     232           8 :         padding: MaterialStateProperty.all(EdgeInsets.all(20)),
     233             :       ),
     234             :     ),
     235           4 :     filledButtonTheme: FilledButtonThemeData(
     236           4 :       style: ButtonStyle(
     237           4 :         backgroundColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor : opaque.current().defaultButtonColor),
     238          12 :         foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor),
     239           4 :         overlayColor: MaterialStateProperty.resolveWith(
     240           0 :           (states) => (states.contains(MaterialState.pressed) && states.contains(MaterialState.hovered))
     241           0 :               ? opaque.current().defaultButtonActiveColor
     242           0 :               : states.contains(MaterialState.disabled)
     243           0 :               ? opaque.current().defaultButtonDisabledColor
     244             :               : null,
     245             :         ),
     246             :         enableFeedback: true,
     247          12 :         textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)),
     248           8 :         padding: MaterialStateProperty.all(EdgeInsets.all(20)),
     249             :       ),
     250             :     ),
     251           4 :     outlinedButtonTheme: OutlinedButtonThemeData(
     252           4 :       style: ButtonStyle(
     253           4 :         backgroundColor: MaterialStateProperty.resolveWith((states) => states.contains(MaterialState.disabled) ? opaque.current().defaultButtonDisabledColor : opaque.current().backgroundMainColor),
     254          12 :         foregroundColor: MaterialStateProperty.all(opaque.current().mainTextColor),
     255          16 :         side: MaterialStateProperty.all(BorderSide(color: opaque.current().defaultButtonColor)),
     256           4 :         overlayColor: MaterialStateProperty.resolveWith(
     257           0 :           (states) => (states.contains(MaterialState.pressed) && states.contains(MaterialState.hovered))
     258           0 :               ? opaque.current().defaultButtonActiveColor
     259           0 :               : states.contains(MaterialState.disabled)
     260           0 :               ? opaque.current().defaultButtonDisabledColor
     261             :               : null,
     262             :         ),
     263             :         enableFeedback: true,
     264          12 :         textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)),
     265           8 :         padding: MaterialStateProperty.all(EdgeInsets.all(20)),
     266             :       ),
     267             :     ),
     268          20 :     scrollbarTheme: ScrollbarThemeData(thumbVisibility: MaterialStateProperty.all(false), thumbColor: MaterialStateProperty.all(opaque.current().scrollbarDefaultColor)),
     269           4 :     sliderTheme: SliderThemeData(
     270           8 :       activeTrackColor: opaque.current().defaultButtonColor, // color of slider bar for left active region
     271           8 :       inactiveTrackColor: opaque.theme.defaultButtonDisabledColor, // color of slider bar for right inactive region
     272           8 :       thumbColor: opaque.current().mainTextColor, // color of slider widget
     273           8 :       overlayColor: opaque.current().mainTextColor, // color around active widget
     274           8 :       valueIndicatorColor: opaque.current().backgroundHilightElementColor,
     275          20 :       valueIndicatorTextStyle: opaque.scaleFonts(defaultDropDownMenuItemTextStyle).copyWith(color: opaque.current().hilightElementColor),
     276           4 :       valueIndicatorShape: RectangularSliderValueIndicatorShape(), //RoundSliderThumbShape(),
     277             :     ),
     278           4 :     tabBarTheme: TabBarThemeData(
     279           8 :       labelColor: opaque.current().mainTextColor,
     280           8 :       unselectedLabelColor: opaque.current().mainTextColor,
     281          16 :       indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor)),
     282          20 :       labelStyle: opaque.scaleFonts(defaultTextButtonStyle).copyWith(color: opaque.current().mainTextColor),
     283          20 :       unselectedLabelStyle: opaque.scaleFonts(defaultTextStyle).copyWith(color: opaque.current().mainTextColor),
     284             :       tabAlignment: TabAlignment.center,
     285             :     ),
     286           4 :     dialogTheme: DialogThemeData(
     287           8 :       backgroundColor: opaque.current().backgroundPaneColor,
     288          12 :       titleTextStyle: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, color: opaque.current().mainTextColor),
     289          12 :       contentTextStyle: TextStyle(fontFamily: "Inter", color: opaque.current().mainTextColor),
     290             :     ),
     291           4 :     textTheme: TextTheme(
     292             :       // NOTE: The following font scales were arrived at after consulting the material text scale
     293             :       // docs: https://m3.material.io/styles/typography/type-scale-tokens and some trial and error
     294          20 :       displaySmall: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor),
     295          20 :       displayMedium: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor),
     296          20 :       displayLarge: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor),
     297          20 :       titleSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor),
     298          20 :       titleLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor),
     299          20 :       titleMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 20.0, color: opaque.current().mainTextColor),
     300          20 :       bodySmall: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 12.0, color: opaque.current().mainTextColor),
     301          20 :       bodyMedium: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor),
     302          20 :       bodyLarge: TextStyle(fontFamily: "Inter", fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor),
     303          20 :       headlineSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 24.0, color: opaque.current().mainTextColor),
     304          20 :       headlineMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 26.0, color: opaque.current().mainTextColor),
     305          20 :       headlineLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: opaque.fontScaling * 28.0, color: opaque.current().mainTextColor),
     306          20 :       labelSmall: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w100, fontSize: opaque.fontScaling * 14.0, color: opaque.current().mainTextColor),
     307          20 :       labelMedium: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w300, fontSize: opaque.fontScaling * 16.0, color: opaque.current().mainTextColor),
     308          20 :       labelLarge: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w200, fontSize: opaque.fontScaling * 18.0, color: opaque.current().mainTextColor),
     309             :     ),
     310           4 :     switchTheme: SwitchThemeData(
     311          12 :       overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor),
     312          12 :       thumbColor: MaterialStateProperty.all(opaque.current().mainTextColor),
     313          12 :       trackColor: MaterialStateProperty.all(opaque.current().dropShadowColor),
     314             :     ),
     315             :     // the only way to change the text Selection Context Menu Color ?!
     316          12 :     brightness: opaque.current().mode == mode_dark ? Brightness.dark : Brightness.light,
     317           4 :     floatingActionButtonTheme: FloatingActionButtonThemeData(
     318           8 :       foregroundColor: opaque.current().mainTextColor,
     319           8 :       backgroundColor: opaque.current().defaultButtonColor,
     320           8 :       hoverColor: opaque.current().defaultButtonActiveColor,
     321             :       enableFeedback: true,
     322           8 :       splashColor: opaque.current().defaultButtonActiveColor,
     323             :     ),
     324           4 :     textSelectionTheme: TextSelectionThemeData(
     325           8 :       cursorColor: opaque.current().textfieldSelectionColor,
     326           8 :       selectionColor: opaque.current().textfieldSelectionColor,
     327           8 :       selectionHandleColor: opaque.current().textfieldSelectionColor,
     328             :     ),
     329          16 :     popupMenuTheme: PopupMenuThemeData(color: opaque.current().backgroundPaneColor.withOpacity(0.9)),
     330           4 :     snackBarTheme: SnackBarThemeData(
     331           8 :       backgroundColor: opaque.current().snackbarBackgroundColor,
     332          12 :       contentTextStyle: TextStyle(color: opaque.current().snackbarTextColor),
     333             :     ),
     334             :   );
     335             : }

Generated by: LCOV version 1.14