179 lines
6.4 KiB
Python
179 lines
6.4 KiB
Python
"""Менеджер тем и масштаба приложения с сохранением в localStorage браузера."""
|
||
import flet as ft
|
||
import designer as ds
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Темы: каждый цвет surface должен КОНТРАСТИРОВАТЬ с background,
|
||
# чтобы карточки, поля и кнопки не сливались с фоном.
|
||
# ---------------------------------------------------------------------------
|
||
THEMES: dict[str, dict] = {
|
||
"default": {
|
||
"name": "ЦОПП (оранжевая)",
|
||
# фон страницы
|
||
"background": "#F4F6F8",
|
||
# карточки / формы — светлее фона
|
||
"surface": "#FFFFFF",
|
||
# альтернативные блоки — чуть темнее фона
|
||
"surface_alt": "#E4E8ED",
|
||
},
|
||
"warm": {
|
||
"name": "Тёплая",
|
||
"background": "#FFF3E6",
|
||
"surface": "#FFFFFF",
|
||
"surface_alt": "#FFD9B3",
|
||
},
|
||
"blue": {
|
||
"name": "Синяя",
|
||
"background": "#E3F2FD",
|
||
"surface": "#FFFFFF",
|
||
"surface_alt": "#B8D9F5",
|
||
},
|
||
"green": {
|
||
"name": "Зелёная",
|
||
"background": "#EDF7E7",
|
||
"surface": "#FFFFFF",
|
||
"surface_alt": "#BEE3B0",
|
||
},
|
||
"dark": {
|
||
"name": "Тёмная",
|
||
"background": "#1E2A3A",
|
||
"surface": "#2D3F54",
|
||
"surface_alt": "#3D5068",
|
||
},
|
||
}
|
||
|
||
_SCALE_KEY = "ui_scale"
|
||
_THEME_KEY = "ui_theme"
|
||
|
||
# Допустимый диапазон масштаба
|
||
SCALE_MIN = 0.8
|
||
SCALE_MAX = 2.5
|
||
SCALE_STEP = 0.1
|
||
SCALE_DEFAULT = 1.7
|
||
|
||
# Автомасштаб по ширине экрана
|
||
_MOBILE_BREAKPOINT = 600 # px: меньше — телефон, больше — ПК/планшет
|
||
SCALE_MOBILE = 1.0
|
||
SCALE_DESKTOP = 2.0
|
||
|
||
|
||
def auto_scale(page: ft.Page) -> float:
|
||
"""Вернуть масштаб на основе ширины viewport браузера."""
|
||
try:
|
||
w = page.width or 0
|
||
return SCALE_MOBILE if w < _MOBILE_BREAKPOINT else SCALE_DESKTOP
|
||
except Exception:
|
||
return SCALE_DEFAULT
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Серверное хранилище настроек по user_id.
|
||
# В памяти контейнера — переживает F5 в браузере, сбрасывается при рестарте.
|
||
# ---------------------------------------------------------------------------
|
||
_user_prefs: dict[str, dict] = {} # user_id -> {"theme": str, "scale": float}
|
||
|
||
|
||
def _get_uid(page: ft.Page) -> str:
|
||
return page.session.store.get("user_id") or ""
|
||
|
||
|
||
def _prefs_for(user_id: str) -> dict:
|
||
return _user_prefs.get(user_id, {"theme": "default", "scale": SCALE_DEFAULT})
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Публичный API
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def apply_theme(page: ft.Page, theme_id: str | None = None) -> None:
|
||
"""Применить тему: обновить ds.colors и page.bgcolor."""
|
||
if theme_id is None:
|
||
uid = _get_uid(page)
|
||
theme_id = _prefs_for(uid)["theme"] if uid else "default"
|
||
if theme_id not in THEMES:
|
||
theme_id = "default"
|
||
|
||
t = THEMES[theme_id]
|
||
ds.colors.background = t["background"]
|
||
ds.colors.surface = t["surface"]
|
||
ds.colors.surface_alt = t["surface_alt"]
|
||
|
||
page.bgcolor = t["background"]
|
||
# кешируем в session.store для быстрого доступа внутри сессии
|
||
page.session.store.set(_THEME_KEY, theme_id)
|
||
|
||
|
||
def apply_scale(page: ft.Page, scale: float | None = None) -> None:
|
||
"""Применить масштаб: обновить ds.SCALE.
|
||
|
||
Если масштаб не задан явно и у пользователя нет сохранённого —
|
||
определяется автоматически по ширине экрана (мобильный / ПК).
|
||
"""
|
||
if scale is None:
|
||
uid = _get_uid(page)
|
||
if uid and uid in _user_prefs:
|
||
# есть явно сохранённая настройка пользователя
|
||
scale = _user_prefs[uid]["scale"]
|
||
else:
|
||
# первый вход — автоопределение по ширине экрана
|
||
scale = auto_scale(page)
|
||
scale = max(SCALE_MIN, min(SCALE_MAX, float(scale)))
|
||
ds.SCALE = scale
|
||
page.session.store.set(_SCALE_KEY, str(scale))
|
||
|
||
|
||
def save_preferences(page: ft.Page, theme_id: str, scale: float,
|
||
user_id: str | None = None) -> None:
|
||
"""Сохранить тему и масштаб. Хранятся в памяти сервера по user_id."""
|
||
uid = user_id or _get_uid(page)
|
||
if uid:
|
||
_user_prefs[uid] = {"theme": theme_id, "scale": float(scale)}
|
||
# также пишем в session.store — нужно для apply_* внутри текущей сессии
|
||
page.session.store.set(_THEME_KEY, theme_id)
|
||
page.session.store.set(_SCALE_KEY, str(scale))
|
||
|
||
|
||
def load_theme_id(page: ft.Page) -> str:
|
||
# сначала session.store (уже применено), потом serv dict
|
||
v = page.session.store.get(_THEME_KEY)
|
||
if v and v in THEMES:
|
||
return v
|
||
uid = _get_uid(page)
|
||
v2 = _prefs_for(uid)["theme"] if uid else "default"
|
||
return v2 if v2 in THEMES else "default"
|
||
|
||
|
||
def load_scale(page: ft.Page) -> float:
|
||
v = page.session.store.get(_SCALE_KEY)
|
||
try:
|
||
if v is not None:
|
||
return float(v)
|
||
except ValueError:
|
||
pass
|
||
uid = _get_uid(page)
|
||
return _prefs_for(uid)["scale"] if uid else SCALE_DEFAULT
|
||
|
||
|
||
def theme_list() -> list[dict]:
|
||
return [{"id": k, "name": v["name"]} for k, v in THEMES.items()]
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Устаревший алиас (оставлен для совместимости с роутером)
|
||
# ---------------------------------------------------------------------------
|
||
class ThemeManager:
|
||
THEMES = THEMES
|
||
|
||
@staticmethod
|
||
def get_theme_list():
|
||
return theme_list()
|
||
|
||
@staticmethod
|
||
def load_theme_preference(page):
|
||
return load_theme_id(page)
|
||
|
||
@staticmethod
|
||
def save_theme_preference(page, theme_id):
|
||
save_preferences(page, theme_id, load_scale(page))
|