import 'dart:convert'; import 'dart:io'; import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/opaque.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:yaml/yaml.dart'; import 'package:path/path.dart' as path; import '../main.dart'; Future>> loadBuiltinThemes() async { final manifestJson = await rootBundle.loadString('AssetManifest.json'); final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes')); Map> themes = Map(); for (String themefile in themesList) { if (themefile.substring(themefile.length - 4) != ".yml") { continue; } try { var data = await loadAssetYamlTheme(themefile); if (data != null) { // remove "assets/themes" and "theme.yml" from name var themename = themefile.substring(14, themefile.lastIndexOf("/")); themes[themename] = data; } } catch (e) { print("Failed to load theme: $themefile with exception: $e"); } } return themes; } Future>> loadCustomThemes(String themesDirPath) async { Map> themes = Map(); Directory themesDir = Directory(themesDirPath); if (!themesDir.existsSync()) { themesDir = await themesDir.create(); } await themesDir.list(recursive: false).forEach((themeDir) async { //final themeDir = Directory(path.join(themesDir, themedir)); File themefile = File(path.join(themeDir.path, "theme.yml")); if (themefile.existsSync()) { try { var data = await loadFileYamlTheme(themefile.path, themeDir.path); if (data != null) { themes[path.basename(themeDir.path)] = data; } } catch (e) { print("Failed to load theme: $themefile with exception: $e"); } } }); return themes; } Future readAssetYamlTheme(String themefile) async { final contents = await rootBundle.loadString(themefile); return loadYaml(contents); } Future?> loadAssetYamlTheme(String themefile) async { final yml = await readAssetYamlTheme(themefile); if (yml == null) { print("Error: failed to load theme: $themefile"); return null; } return loadYamlTheme(yml); } Future?> loadFileYamlTheme(String themefile, String assetsDir) async { final file = File(themefile); if (!file.existsSync()) { print("Error: theme file: $themefile does not exist"); return null; } final contents = file.readAsStringSync(); final yml = await loadYaml(contents); if (yml == null) { print("Error: failed to load theme: $themefile"); return null; } return loadYamlTheme(yml, assetsDir); } Future?> loadYamlTheme(YamlMap yml, [String? assetsDir]) async { Map subthemes = Map(); if ((yml["themes"] as YamlMap).containsKey(mode_dark)) { subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark, assetsDir); } if ((yml["themes"] as YamlMap).containsKey(mode_light)) { subthemes[mode_light] = YmlTheme(yml, yml["themes"]["name"], mode_light, assetsDir); } return subthemes; } class YmlTheme extends OpaqueThemeType { late YamlMap yml; late String mode; late String theme; late String? assetsDir; late OpaqueThemeType fallbackTheme; YmlTheme(YamlMap yml, theme, mode, assetsDir) { this.yml = yml; this.theme = theme; this.mode = mode; this.assetsDir = assetsDir; if (mode == mode_dark) { fallbackTheme = CwtchDark(); } else { fallbackTheme = CwtchLight(); } } Color? getColor(String name) { var val = yml["themes"][mode]["theme"][name]; if (!(val is int)) { val = yml["themes"][mode]["theme"][val] ?? val; } if (!(val is int)) { val = yml["themes"][mode]?["colors"]?[val] ?? val; } if (!(val is int)) { val = yml["colors"]?[val]; } if (!(val is int)) { return null; } if (val <= 0xFFFFFF) { return Color(0xFF000000 + val); } else { return Color(val); } } String? getImage(String name) { var val = yml["themes"][mode]["theme"]?[name]; if (val != null) { if (assetsDir == null) { return path.join("assets", "themes", yml["themes"]["name"], val); } else { return val; } } return null; } get backgroundMainColor => getColor("backgroundMainColor") ?? fallbackTheme.backgroundMainColor; get backgroundPaneColor => getColor("backgroundPaneColor") ?? fallbackTheme.backgroundPaneColor; get topbarColor => getColor("topbarColor") ?? fallbackTheme.topbarColor; get mainTextColor => getColor("mainTextColor") ?? fallbackTheme.mainTextColor; get hilightElementColor => getColor("hilightElementColor") ?? fallbackTheme.hilightElementColor; get backgroundHilightElementColor => getColor("backgroundHilightElementColor") ?? fallbackTheme.backgroundHilightElementColor; get sendHintTextColor => getColor("sendHintTextColor") ?? fallbackTheme.sendHintTextColor; get defaultButtonColor => getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor; get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor); get defaultButtonTextColor => getColor("defaultButtonTextColor") ?? fallbackTheme.defaultButtonTextColor; get defaultButtonDisabledColor => getColor("defaultButtonDisabledColor") ?? fallbackTheme.defaultButtonDisabledColor; get textfieldBackgroundColor => getColor("textfieldBackgroundColor") ?? fallbackTheme.textfieldBackgroundColor; get textfieldBorderColor => getColor("textfieldBorderColor") ?? fallbackTheme.textfieldBorderColor; get textfieldHintColor => getColor("textfieldHintColor") ?? fallbackTheme.textfieldHintColor; get textfieldErrorColor => getColor("textfieldErrorColor") ?? fallbackTheme.textfieldErrorColor; get textfieldSelectionColor => getColor("textfieldSelectionColor") ?? fallbackTheme.textfieldSelectionColor; get scrollbarDefaultColor => getColor("scrollbarDefaultColor") ?? fallbackTheme.scrollbarDefaultColor; get portraitBackgroundColor => getColor("portraitBackgroundColor") ?? fallbackTheme.portraitBackgroundColor; get portraitOnlineBorderColor => getColor("portraitOnlineBorderColor") ?? fallbackTheme.portraitOnlineBorderColor; get portraitOfflineBorderColor => getColor("portraitOfflineBorderColor") ?? fallbackTheme.portraitOfflineBorderColor; get portraitBlockedBorderColor => getColor("portraitBlockedBorderColor") ?? fallbackTheme.portraitBlockedBorderColor; get portraitBlockedTextColor => getColor("portraitBlockedTextColor") ?? fallbackTheme.portraitBlockedTextColor; get portraitContactBadgeColor => getColor("portraitContactBadgeColor") ?? fallbackTheme.portraitContactBadgeColor; get portraitContactBadgeTextColor => getColor("portraitContactBadgeTextColor") ?? fallbackTheme.portraitContactBadgeTextColor; get portraitProfileBadgeColor => getColor("portraitProfileBadgeColor") ?? fallbackTheme.portraitProfileBadgeColor; get portraitProfileBadgeTextColor => getColor("portraitProfileBadgeTextColor") ?? fallbackTheme.portraitProfileBadgeTextColor; get portraitOnlineAwayColor => getColor("portraitOnlineAwayColor") ?? fallbackTheme.portraitOnlineAwayColor; get portraitOnlineBusyColor => getColor("portraitOnlineBusyColor") ?? fallbackTheme.portraitOnlineBusyColor; get dropShadowColor => getColor("dropShadowColor") ?? fallbackTheme.dropShadowColor; get toolbarIconColor => getColor("toolbarIconColor") ?? fallbackTheme.toolbarIconColor; get chatReactionIconColor => getColor("chatReactionIconColor") ?? fallbackTheme.chatReactionIconColor; get messageFromMeBackgroundColor => getColor("messageFromMeBackgroundColor") ?? fallbackTheme.messageFromMeBackgroundColor; get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor; get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor; get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor; get messageSelectionColor => getColor("messageSelectionColor") ?? fallbackTheme.messageSelectionColor; get menuBackgroundColor => getColor("menuBackgroundColor") ?? fallbackTheme.menuBackgroundColor; get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor; get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor; // Images get chatImageColor => getColor("chatImageColor") ?? fallbackTheme.chatImageColor; get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage; ImageProvider loadImage(String key, {BuildContext? context}) { File f = File(key); if (f.existsSync()) { return FileImage(f); } final assetsDir = this.assetsDir; if (assetsDir != null) { File f = File(path.join(assetsDir, key)); if (f.existsSync()) { return FileImage(f); } } if (context != null) { File af = File(path.join(Provider.of(context, listen: false).cwtch.getAssetsDir(), key)); if (af.existsSync()) { return FileImage(af); } } return AssetImage(key); } }