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