"""Изменяет цветовую тему приложения и иконку "color_theme": { "enabled": true, "logo": { "gradient": { "angle": 0.0, "start_color": "#ffccff00", "end_color": "#ffcccc00" }, "ears_color": "#ffffd0d0" }, "colors": { "primary": "#ccff00", "secondary": "#ffcccc00", "background": "#ffffff", "text": "#000000" } } """ __author__ = "wowlikon " __version__ = "1.0.0" from typing import Any, Dict from lxml import etree from pydantic import BaseModel, Field, model_validator from utils.config import PatchTemplate from utils.public import change_color, insert_after_color, insert_after_public class Gradient(BaseModel): priority: int = Field(frozen=True, exclude=True, default=0) angle: float = Field(0.0, description="Угол градиента") start_color: str = Field("#ffccff00", description="Начальный цвет градиента") end_color: str = Field("#ffcccc00", description="Конечный цвет градиента") class Logo(BaseModel): gradient: Gradient = Field( default_factory=Gradient, description="Настройки градиента" ) ears_color: str = Field("#ffd0d0d0", description="Цвет ушей логотипа") class Colors(BaseModel): primary: str = Field("#ccff00", description="Основной цвет") secondary: str = Field("#ffcccc00", description="Вторичный цвет") background: str = Field("#ffffff", description="Фоновый цвет") text: str = Field("#000000", description="Цвет текста") class Patch(PatchTemplate): priority: int = Field(frozen=True, exclude=True, default=0) logo: Logo = Field(default_factory=Logo, description="Настройки цветов логотипа") colors: Colors = Field(default_factory=Colors, description="Настройки цветов") @model_validator(mode="before") @classmethod def validate_nested(cls, data): if isinstance(data, dict): if "logo" in data and isinstance(data["logo"], dict): data["logo"] = Logo(**data["logo"]) if "colors" in data and isinstance(data["colors"], dict): data["colors"] = Colors(**data["colors"]) return data def hex_to_lottie(hex_color: str) -> tuple[float, float, float]: hex_color = hex_color.lstrip("#") hex_color = hex_color[2:] if len(hex_color) == 8 else hex_color return ( int(hex_color[:2], 16) / 255.0, int(hex_color[2:4], 16) / 255.0, int(hex_color[4:6], 16) / 255.0, ) def apply(self, base: Dict[str, Any]) -> bool: main_color = self.colors.primary splash_color = self.colors.secondary # Обновление сообщения об отсутствии подключения with open( "./decompiled/assets/no_connection.html", "r", encoding="utf-8" ) as file: file_contents = file.read() new_contents = file_contents.replace("#f04e4e", main_color) with open( "./decompiled/assets/no_connection.html", "w", encoding="utf-8" ) as file: file.write(new_contents) # Суффиксы лого drawable_types = ["", "-night"] for drawable_type in drawable_types: # Градиент лого приложения file_path = f"./decompiled/res/drawable{drawable_type}/$logo__0.xml" parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(file_path, parser) root = tree.getroot() # Замена атрибутов значениями из конфигурации root.set( f"{{{base['xml_ns']['android']}}}angle", str(self.logo.gradient.angle) ) root.set( f"{{{base['xml_ns']['android']}}}startColor", self.logo.gradient.start_color, ) root.set( f"{{{base['xml_ns']['android']}}}endColor", self.logo.gradient.end_color ) # Сохранение tree.write( file_path, pretty_print=True, xml_declaration=True, encoding="utf-8" ) # Замена анимации лого file_path = ( f"./decompiled/res/drawable{drawable_type}/$logo_splash_anim__0.xml" ) parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(file_path, parser) root = tree.getroot() for el in root.findall("path", namespaces=base["xml_ns"]): name = el.get(f"{{{base['xml_ns']['android']}}}name") if name == "path": el.set( f"{{{base['xml_ns']['android']}}}fillColor", self.colors.secondary, ) elif name in ["path_1", "path_2"]: el.set( f"{{{base['xml_ns']['android']}}}fillColor", self.logo.ears_color, ) # Сохранение tree.write( file_path, pretty_print=True, xml_declaration=True, encoding="utf-8" ) for filename in ["$ic_launcher_foreground__0", "$ic_banner_foreground__0"]: file_path = f"./decompiled/res/drawable-v24/{filename}.xml" parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(file_path, parser) root = tree.getroot() # Замена атрибутов значениями из конфигурации root.set( f"{{{base['xml_ns']['android']}}}angle", str(self.logo.gradient.angle) ) items = root.findall("item", namespaces=base["xml_ns"]) assert len(items) == 2 items[0].set( f"{{{base['xml_ns']['android']}}}color", self.logo.gradient.start_color ) items[1].set( f"{{{base['xml_ns']['android']}}}color", self.logo.gradient.end_color ) # Сохранение tree.write( file_path, pretty_print=True, xml_declaration=True, encoding="utf-8" ) # Добаление новых цветов для темы insert_after_public("carmine", "custom_color") insert_after_public("carmine_alpha_10", "custom_color_alpha_10") insert_after_color( "carmine", "custom_color", main_color[0] + "ff" + main_color[1:] ) insert_after_color( "carmine_alpha_10", "custom_color_alpha_10", main_color[0] + "1a" + main_color[1:], ) # Замена цветов change_color("accent_alpha_10", main_color[0] + "1a" + main_color[1:]) change_color("accent_alpha_20", main_color[0] + "33" + main_color[1:]) change_color("accent_alpha_50", main_color[0] + "80" + main_color[1:]) change_color("accent_alpha_70", main_color[0] + "b3" + main_color[1:]) change_color("colorAccent", main_color[0] + "ff" + main_color[1:]) change_color("link_color", main_color[0] + "ff" + main_color[1:]) change_color("link_color_alpha_70", main_color[0] + "b3" + main_color[1:]) change_color("refresh_progress", main_color[0] + "ff" + main_color[1:]) change_color("ic_launcher_background", "#ff000000") change_color("bottom_nav_indicator_active", "#ffffffff") change_color( "bottom_nav_indicator_icon_checked", main_color[0] + "ff" + main_color[1:] ) change_color( "bottom_nav_indicator_label_checked", main_color[0] + "ff" + main_color[1:] ) return True