Line data Source code
1 : import 'dart:convert'; 2 : import 'dart:io'; 3 : 4 : import 'package:cwtch/themes/cwtch.dart'; 5 : import 'package:cwtch/themes/opaque.dart'; 6 : import 'package:flutter/material.dart'; 7 : import 'package:flutter/services.dart'; 8 : import 'package:provider/provider.dart'; 9 : import 'package:yaml/yaml.dart'; 10 : import 'package:path/path.dart' as path; 11 : 12 : import '../main.dart'; 13 : 14 0 : Future<Map<String, Map<String, OpaqueThemeType>>> loadBuiltinThemes() async { 15 0 : final manifestJson = await rootBundle.loadString('AssetManifest.json'); 16 0 : final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes')); 17 0 : Map<String, Map<String, OpaqueThemeType>> themes = Map(); 18 : 19 0 : for (String themefile in themesList) { 20 0 : if (themefile.substring(themefile.length - 4) != ".yml") { 21 : continue; 22 : } 23 : 24 : try { 25 0 : var data = await loadAssetYamlTheme(themefile); 26 : 27 : if (data != null) { 28 : // remove "assets/themes" and "theme.yml" from name 29 0 : var themename = themefile.substring(14, themefile.lastIndexOf("/")); 30 0 : themes[themename] = data; 31 : } 32 : } catch (e) { 33 0 : print("Failed to load theme: $themefile with exception: $e"); 34 : } 35 : } 36 : return themes; 37 : } 38 : 39 0 : Future<Map<String, Map<String, OpaqueThemeType>>> loadCustomThemes(String themesDirPath) async { 40 0 : Map<String, Map<String, OpaqueThemeType>> themes = Map(); 41 : 42 0 : Directory themesDir = Directory(themesDirPath); 43 0 : if (!themesDir.existsSync()) { 44 0 : themesDir = await themesDir.create(); 45 : } 46 0 : await themesDir.list(recursive: false).forEach((themeDir) async { 47 : //final themeDir = Directory(path.join(themesDir, themedir)); 48 0 : File themefile = File(path.join(themeDir.path, "theme.yml")); 49 0 : if (themefile.existsSync()) { 50 : try { 51 0 : var data = await loadFileYamlTheme(themefile.path, themeDir.path); 52 : 53 : if (data != null) { 54 0 : themes[path.basename(themeDir.path)] = data; 55 : } 56 : } catch (e) { 57 0 : print("Failed to load theme: $themefile with exception: $e"); 58 : } 59 : } 60 : }); 61 : 62 : return themes; 63 : } 64 : 65 0 : Future<YamlMap?> readAssetYamlTheme(String themefile) async { 66 0 : final contents = await rootBundle.loadString(themefile); 67 0 : return loadYaml(contents); 68 : } 69 : 70 0 : Future<Map<String, OpaqueThemeType>?> loadAssetYamlTheme(String themefile) async { 71 0 : final yml = await readAssetYamlTheme(themefile); 72 : 73 : if (yml == null) { 74 0 : print("Error: failed to load theme: $themefile"); 75 : return null; 76 : } 77 0 : return loadYamlTheme(yml); 78 : } 79 : 80 0 : Future<Map<String, OpaqueThemeType>?> loadFileYamlTheme(String themefile, String assetsDir) async { 81 0 : final file = File(themefile); 82 0 : if (!file.existsSync()) { 83 0 : print("Error: theme file: $themefile does not exist"); 84 : return null; 85 : } 86 0 : final contents = file.readAsStringSync(); 87 0 : final yml = await loadYaml(contents); 88 : if (yml == null) { 89 0 : print("Error: failed to load theme: $themefile"); 90 : return null; 91 : } 92 0 : return loadYamlTheme(yml, assetsDir); 93 : } 94 : 95 0 : Future<Map<String, OpaqueThemeType>?> loadYamlTheme(YamlMap yml, [String? assetsDir]) async { 96 0 : Map<String, OpaqueThemeType> subthemes = Map(); 97 0 : if ((yml["themes"] as YamlMap).containsKey(mode_dark)) { 98 0 : subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark, assetsDir); 99 : } 100 0 : if ((yml["themes"] as YamlMap).containsKey(mode_light)) { 101 0 : subthemes[mode_light] = YmlTheme(yml, yml["themes"]["name"], mode_light, assetsDir); 102 : } 103 : return subthemes; 104 : } 105 : 106 : class YmlTheme extends OpaqueThemeType { 107 : late YamlMap yml; 108 : late String mode; 109 : late String theme; 110 : late String? assetsDir; 111 : late OpaqueThemeType fallbackTheme; 112 : 113 0 : YmlTheme(YamlMap yml, theme, mode, assetsDir) { 114 0 : this.yml = yml; 115 0 : this.theme = theme; 116 0 : this.mode = mode; 117 0 : this.assetsDir = assetsDir; 118 0 : if (mode == mode_dark) { 119 0 : fallbackTheme = CwtchDark(); 120 : } else { 121 0 : fallbackTheme = CwtchLight(); 122 : } 123 : } 124 : 125 0 : Color? getColor(String name) { 126 0 : var val = yml["themes"][mode]["theme"][name]; 127 0 : if (!(val is int)) { 128 0 : val = yml["themes"][mode]["theme"][val] ?? val; 129 : } 130 0 : if (!(val is int)) { 131 0 : val = yml["themes"][mode]?["colors"]?[val] ?? val; 132 : } 133 0 : if (!(val is int)) { 134 0 : val = yml["colors"]?[val]; 135 : } 136 0 : if (!(val is int)) { 137 : return null; 138 : } 139 : 140 0 : if (val <= 0xFFFFFF) { 141 0 : return Color(0xFF000000 + val); 142 : } else { 143 0 : return Color(val); 144 : } 145 : } 146 : 147 0 : String? getImage(String name) { 148 0 : var val = yml["themes"][mode]["theme"]?[name]; 149 : if (val != null) { 150 0 : if (assetsDir == null) { 151 0 : return path.join("assets", "themes", yml["themes"]["name"], val); 152 : } else { 153 : return val; 154 : } 155 : } 156 : return null; 157 : } 158 : 159 0 : get backgroundMainColor => getColor("backgroundMainColor") ?? fallbackTheme.backgroundMainColor; 160 0 : get backgroundPaneColor => getColor("backgroundPaneColor") ?? fallbackTheme.backgroundPaneColor; 161 0 : get topbarColor => getColor("topbarColor") ?? fallbackTheme.topbarColor; 162 0 : get mainTextColor => getColor("mainTextColor") ?? fallbackTheme.mainTextColor; 163 0 : get hilightElementColor => getColor("hilightElementColor") ?? fallbackTheme.hilightElementColor; 164 0 : get backgroundHilightElementColor => getColor("backgroundHilightElementColor") ?? fallbackTheme.backgroundHilightElementColor; 165 0 : get sendHintTextColor => getColor("sendHintTextColor") ?? fallbackTheme.sendHintTextColor; 166 0 : get defaultButtonColor => getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor; 167 0 : get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor); 168 0 : get defaultButtonTextColor => getColor("defaultButtonTextColor") ?? fallbackTheme.defaultButtonTextColor; 169 0 : get defaultButtonDisabledColor => getColor("defaultButtonDisabledColor") ?? fallbackTheme.defaultButtonDisabledColor; 170 0 : get textfieldBackgroundColor => getColor("textfieldBackgroundColor") ?? fallbackTheme.textfieldBackgroundColor; 171 0 : get textfieldBorderColor => getColor("textfieldBorderColor") ?? fallbackTheme.textfieldBorderColor; 172 0 : get textfieldHintColor => getColor("textfieldHintColor") ?? fallbackTheme.textfieldHintColor; 173 0 : get textfieldErrorColor => getColor("textfieldErrorColor") ?? fallbackTheme.textfieldErrorColor; 174 0 : get textfieldSelectionColor => getColor("textfieldSelectionColor") ?? fallbackTheme.textfieldSelectionColor; 175 0 : get scrollbarDefaultColor => getColor("scrollbarDefaultColor") ?? fallbackTheme.scrollbarDefaultColor; 176 0 : get portraitBackgroundColor => getColor("portraitBackgroundColor") ?? fallbackTheme.portraitBackgroundColor; 177 0 : get portraitOnlineBorderColor => getColor("portraitOnlineBorderColor") ?? fallbackTheme.portraitOnlineBorderColor; 178 0 : get portraitOfflineBorderColor => getColor("portraitOfflineBorderColor") ?? fallbackTheme.portraitOfflineBorderColor; 179 0 : get portraitBlockedBorderColor => getColor("portraitBlockedBorderColor") ?? fallbackTheme.portraitBlockedBorderColor; 180 0 : get portraitBlockedTextColor => getColor("portraitBlockedTextColor") ?? fallbackTheme.portraitBlockedTextColor; 181 0 : get portraitContactBadgeColor => getColor("portraitContactBadgeColor") ?? fallbackTheme.portraitContactBadgeColor; 182 0 : get portraitContactBadgeTextColor => getColor("portraitContactBadgeTextColor") ?? fallbackTheme.portraitContactBadgeTextColor; 183 0 : get portraitProfileBadgeColor => getColor("portraitProfileBadgeColor") ?? fallbackTheme.portraitProfileBadgeColor; 184 0 : get portraitProfileBadgeTextColor => getColor("portraitProfileBadgeTextColor") ?? fallbackTheme.portraitProfileBadgeTextColor; 185 0 : get portraitOnlineAwayColor => getColor("portraitOnlineAwayColor") ?? fallbackTheme.portraitOnlineAwayColor; 186 0 : get portraitOnlineBusyColor => getColor("portraitOnlineBusyColor") ?? fallbackTheme.portraitOnlineBusyColor; 187 0 : get dropShadowColor => getColor("dropShadowColor") ?? fallbackTheme.dropShadowColor; 188 0 : get toolbarIconColor => getColor("toolbarIconColor") ?? fallbackTheme.toolbarIconColor; 189 0 : get toolbarBackgroundColor => getColor("toolbarBackgroundColor") ?? fallbackTheme.toolbarBackgroundColor; 190 0 : get chatReactionIconColor => getColor("chatReactionIconColor") ?? fallbackTheme.chatReactionIconColor; 191 0 : get messageFromMeBackgroundColor => getColor("messageFromMeBackgroundColor") ?? fallbackTheme.messageFromMeBackgroundColor; 192 0 : get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor; 193 0 : get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor; 194 0 : get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor; 195 0 : get messageSelectionColor => getColor("messageSelectionColor") ?? fallbackTheme.messageSelectionColor; 196 0 : get menuBackgroundColor => getColor("menuBackgroundColor") ?? fallbackTheme.menuBackgroundColor; 197 0 : get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor; 198 0 : get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor; 199 : 200 : // Images 201 : 202 0 : get chatImageColor => getColor("chatImageColor") ?? fallbackTheme.chatImageColor; 203 0 : get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage; 204 : 205 0 : ImageProvider loadImage(String key, {BuildContext? context}) { 206 0 : File f = File(key); 207 0 : if (f.existsSync()) { 208 0 : return FileImage(f); 209 : } 210 : 211 0 : final assetsDir = this.assetsDir; 212 : if (assetsDir != null) { 213 0 : File f = File(path.join(assetsDir, key)); 214 0 : if (f.existsSync()) { 215 0 : return FileImage(f); 216 : } 217 : } 218 : 219 : if (context != null) { 220 0 : File af = File(path.join(Provider.of<FlwtchState>(context, listen: false).cwtch.getAssetsDir(), key)); 221 0 : if (af.existsSync()) { 222 0 : return FileImage(af); 223 : } 224 : } 225 : 226 0 : return AssetImage(key); 227 : } 228 : }