patch
This commit is contained in:
		
							parent
							
								
									35ad95b8f7
								
							
						
					
					
						commit
						ddb95ae852
					
				@ -63,7 +63,7 @@ class MainController(QStackedWidget):
 | 
			
		||||
            print("[Sync] Запускаем предзагрузку прав доступа...")
 | 
			
		||||
            try:
 | 
			
		||||
                # Запускаем асинхронную функцию в текущем потоке
 | 
			
		||||
                asyncio.run(self.yobble_home_view.preload_permissions())
 | 
			
		||||
                asyncio.run(self.yobble_home_view.preload_permissions()) # TODO добавить из sqlite
 | 
			
		||||
                print("[Sync] Предзагрузка прав доступа завершена.")
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                print(f"[Sync] Ошибка во время предзагрузки прав: {e}")
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import sqlite3
 | 
			
		||||
import aiosqlite
 | 
			
		||||
import os
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
@ -104,14 +105,26 @@ def set_last_login(login: str):
 | 
			
		||||
    conn.commit()
 | 
			
		||||
    conn.close()
 | 
			
		||||
 | 
			
		||||
def get_current_access_token():
 | 
			
		||||
    """
 | 
			
		||||
    Получает access_token для последней активной сессии.
 | 
			
		||||
    :return: access_token или None, если сессия не найдена.
 | 
			
		||||
    """
 | 
			
		||||
    last_login = get_last_login()
 | 
			
		||||
    if not last_login:
 | 
			
		||||
        return None
 | 
			
		||||
# def get_current_access_token():
 | 
			
		||||
#     """
 | 
			
		||||
#     Получает access_token для последней активной сессии.
 | 
			
		||||
#     :return: access_token или None, если сессия не найдена.
 | 
			
		||||
#     """
 | 
			
		||||
#     last_login = get_last_login()
 | 
			
		||||
#     if not last_login:
 | 
			
		||||
#         return None
 | 
			
		||||
    
 | 
			
		||||
    session = get_session(last_login)
 | 
			
		||||
    return session['access_token'] if session else None
 | 
			
		||||
#     session = get_session(last_login)
 | 
			
		||||
#     return session['access_token'] if session else None
 | 
			
		||||
 | 
			
		||||
async def get_current_access_token():
 | 
			
		||||
    async with aiosqlite.connect(DB_PATH) as db:
 | 
			
		||||
        db.row_factory = aiosqlite.Row
 | 
			
		||||
        async with db.execute("SELECT value FROM app_state WHERE key = 'last_login'") as cur:
 | 
			
		||||
            row = await cur.fetchone()
 | 
			
		||||
            if not row:
 | 
			
		||||
                return None
 | 
			
		||||
            last_login = row[0]
 | 
			
		||||
        async with db.execute('SELECT access_token FROM sessions WHERE login = ?', (last_login,)) as cur:
 | 
			
		||||
            row = await cur.fetchone()
 | 
			
		||||
            return row[0] if row else None
 | 
			
		||||
 | 
			
		||||
@ -101,9 +101,11 @@ async def get_user_role(access_token: str):
 | 
			
		||||
    url = f"{config.BASE_URL}/v1/user/role"
 | 
			
		||||
    headers = {"Authorization": f"Bearer {access_token}"}
 | 
			
		||||
 | 
			
		||||
    timeout = httpx.Timeout(connect=5.0, read=10.0, write=5.0, pool=5.0)
 | 
			
		||||
    try:
 | 
			
		||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
			
		||||
        async with httpx.AsyncClient(http2=True, timeout=timeout) as client:
 | 
			
		||||
            response = await client.get(url, headers=headers)
 | 
			
		||||
            print("headers", headers,"response", response, "status", response.json().get("status"))
 | 
			
		||||
 | 
			
		||||
            if response.status_code == 200:
 | 
			
		||||
                data = response.json()
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,9 @@
 | 
			
		||||
  "Регистрация в данный момент отключена.": "Registration is currently disabled.",
 | 
			
		||||
  "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Data validation failed. Check username and password length.",
 | 
			
		||||
 | 
			
		||||
  "Загрузка...": "Загрузка...",
 | 
			
		||||
  "Доступ запрещен": "Access denied",
 | 
			
		||||
 | 
			
		||||
  "Login must be between 3 and 32 characters long": "Login must be between 3 and 32 characters long",
 | 
			
		||||
  "Login must not contain whitespace characters": "Login must not contain whitespace characters",
 | 
			
		||||
  "Login must not start with an underscore": "Login must not start with an underscore",
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,9 @@
 | 
			
		||||
  "Регистрация в данный момент отключена.": "Регистрация в данный момент отключена.",
 | 
			
		||||
  "Данные не прошли валидацию. Проверьте длину логина и пароля.": "Данные не прошли валидацию. Проверьте длину логина и пароля.",
 | 
			
		||||
 | 
			
		||||
  "Загрузка...": "Loading...",
 | 
			
		||||
  "Доступ запрещен": "Доступ запрещен",
 | 
			
		||||
 | 
			
		||||
  "Login must be between 3 and 32 characters long": "Логин должен быть от 3 до 32 символов",
 | 
			
		||||
  "Login must not contain whitespace characters": "Логин не должен содержать пробелы",
 | 
			
		||||
  "Login must not start with an underscore": "Логин не должен начинаться с символа подчёркивания",
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,11 @@
 | 
			
		||||
import asyncio
 | 
			
		||||
import time
 | 
			
		||||
from PySide6.QtWidgets import (
 | 
			
		||||
    QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, 
 | 
			
		||||
    QStackedWidget, QSizePolicy, QGraphicsDropShadowEffect,
 | 
			
		||||
    QGraphicsOpacityEffect, QMessageBox
 | 
			
		||||
)
 | 
			
		||||
from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve
 | 
			
		||||
from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve, QTimer
 | 
			
		||||
from PySide6.QtGui import QColor
 | 
			
		||||
 | 
			
		||||
from app.core.theme import theme_manager
 | 
			
		||||
@ -27,6 +28,8 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        self.setMinimumSize(360, 640)
 | 
			
		||||
        self.permission_cache = set()
 | 
			
		||||
        self.permissions_preloaded = False
 | 
			
		||||
        self.permissions_preloaded = False
 | 
			
		||||
        self.permissions_preloaded_last = 0.0
 | 
			
		||||
 | 
			
		||||
        # --- Основной макет ---
 | 
			
		||||
        # Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом
 | 
			
		||||
@ -52,7 +55,9 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
 | 
			
		||||
        # 3. Нижняя панель навигации
 | 
			
		||||
        self.bottom_bar = self.create_bottom_bar()
 | 
			
		||||
        self.content_stack.setCurrentIndex(2)
 | 
			
		||||
        # self.update_tab_selection(2)
 | 
			
		||||
        # self.content_stack.setCurrentIndex(2)
 | 
			
		||||
        self.switch_tab(2)
 | 
			
		||||
        content_layout.addWidget(self.bottom_bar)
 | 
			
		||||
        
 | 
			
		||||
        main_layout.addWidget(self.content_widget)
 | 
			
		||||
@ -63,6 +68,11 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        self.update_styles()
 | 
			
		||||
        theme_manager.theme_changed.connect(self.update_styles)
 | 
			
		||||
 | 
			
		||||
        # Анти-спам/кулдауны для прав
 | 
			
		||||
        self._permission_checking = set()   # индекс вкладки, где сейчас идёт проверка
 | 
			
		||||
        self._denied_recently = {}          # {index: bool} — активен кулдаун после "Доступ запрещен"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def setup_side_menu(self):
 | 
			
		||||
        """Настраивает боковое меню и оверлей."""
 | 
			
		||||
        # Оверлей для затемнения контента
 | 
			
		||||
@ -152,6 +162,7 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        top_bar_layout.addWidget(self.burger_menu_button)
 | 
			
		||||
 | 
			
		||||
        self.title_label = QLabel("Чаты")
 | 
			
		||||
        #self.title_label = QLabel()
 | 
			
		||||
        self.title_label.setObjectName("TitleLabel")
 | 
			
		||||
        top_bar_layout.addWidget(self.title_label)
 | 
			
		||||
        top_bar_layout.addStretch()
 | 
			
		||||
@ -200,7 +211,7 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        
 | 
			
		||||
        btn_create.setFocusPolicy(Qt.NoFocus)
 | 
			
		||||
 | 
			
		||||
        self.update_tab_selection(2)
 | 
			
		||||
        #self.update_tab_selection(2)
 | 
			
		||||
        return bottom_bar_widget
 | 
			
		||||
 | 
			
		||||
    def create_tab_button(self, icon_text, text, index):
 | 
			
		||||
@ -253,12 +264,12 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
 | 
			
		||||
    def setup_content_pages(self):
 | 
			
		||||
        # Лента
 | 
			
		||||
        self.feed_label = QLabel("Загрузка...")
 | 
			
		||||
        self.feed_label = QLabel(localizer.translate("Загрузка..."))
 | 
			
		||||
        self.feed_label.setAlignment(Qt.AlignCenter)
 | 
			
		||||
        self.content_stack.addWidget(self.feed_label)
 | 
			
		||||
 | 
			
		||||
        # Музыка
 | 
			
		||||
        self.music_label = QLabel("Загрузка...")
 | 
			
		||||
        self.music_label = QLabel(localizer.translate("Загрузка..."))
 | 
			
		||||
        self.music_label.setAlignment(Qt.AlignCenter)
 | 
			
		||||
        self.content_stack.addWidget(self.music_label)
 | 
			
		||||
 | 
			
		||||
@ -272,35 +283,64 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        """Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
 | 
			
		||||
        if index in self.REQUIRED_PERMISSIONS:
 | 
			
		||||
            # сразу переключаем на вкладку (там уже "Загрузка...")
 | 
			
		||||
            self.switch_tab(index)
 | 
			
		||||
            # запускаем асинхронную проверку
 | 
			
		||||
            #self.switch_tab(index)
 | 
			
		||||
 | 
			
		||||
            if self.permissions_preloaded and self.preload_permissions_first==False:
 | 
			
		||||
                now = time.time()
 | 
			
		||||
                elapsed = now - self.permissions_preloaded_last if self.permissions_preloaded_last else float("inf")
 | 
			
		||||
                # если прошло больше 30 секунд или системное время ушло назад → протухло
 | 
			
		||||
                if elapsed >= 30 or elapsed < 0:
 | 
			
		||||
 | 
			
		||||
                    self.permissions_preloaded = False
 | 
			
		||||
            asyncio.create_task(
 | 
			
		||||
                self.check_permissions_and_switch(index, self.REQUIRED_PERMISSIONS[index])
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            # Для вкладок без специальных прав доступа
 | 
			
		||||
            self.switch_tab(index)
 | 
			
		||||
 | 
			
		||||
    async def preload_permissions(self):
 | 
			
		||||
        """Асинхронно предзагружает права доступа без UI."""
 | 
			
		||||
        access_token = get_current_access_token()
 | 
			
		||||
        access_token = await get_current_access_token()
 | 
			
		||||
        if not access_token:
 | 
			
		||||
            print("[Permissions] Preload failed: No access token.")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        success, data = await get_user_role(access_token)
 | 
			
		||||
        if success:
 | 
			
		||||
            user_permissions = data.get("user_permissions", [])
 | 
			
		||||
            for permission_code in self.REQUIRED_PERMISSIONS.values():
 | 
			
		||||
                if permission_code in user_permissions:
 | 
			
		||||
                    self.permission_cache.add(permission_code)
 | 
			
		||||
            user_permissions = set(data.get("user_permissions", []))
 | 
			
		||||
 | 
			
		||||
            # Загружаем ВСЕ права пользователя в кэш
 | 
			
		||||
            self.permission_cache = user_permissions
 | 
			
		||||
            print("self.permission_cache", self.permission_cache)
 | 
			
		||||
 | 
			
		||||
            # for permission_code in self.REQUIRED_PERMISSIONS.values():
 | 
			
		||||
            #     if permission_code in user_permissions:
 | 
			
		||||
            #         self.permission_cache.add(permission_code)
 | 
			
		||||
            
 | 
			
		||||
            self.permissions_preloaded_last = time.time()
 | 
			
		||||
            self.permissions_preloaded = True
 | 
			
		||||
            self.preload_permissions_first = True
 | 
			
		||||
 | 
			
		||||
            print(f"[Permissions] Preloaded. Cache: {self.permission_cache}")
 | 
			
		||||
        else:
 | 
			
		||||
            print(f"[Permissions] Preload failed: {data}")
 | 
			
		||||
 | 
			
		||||
    # def update_current_tab_content(self):
 | 
			
		||||
    #     """Обновляет контент текущей активной вкладки после предзагрузки прав."""
 | 
			
		||||
    #     current_index = self.content_stack.currentIndex()
 | 
			
		||||
        
 | 
			
		||||
    #     # Проверяем, требует ли текущая вкладка прав доступа
 | 
			
		||||
    #     if current_index in self.REQUIRED_PERMISSIONS:
 | 
			
		||||
    #         permission_code = self.REQUIRED_PERMISSIONS[current_index]
 | 
			
		||||
            
 | 
			
		||||
    #         if permission_code in self.permission_cache:
 | 
			
		||||
    #             self.show_real_content(current_index)
 | 
			
		||||
    #         else:
 | 
			
		||||
    #             self.show_denied(current_index)
 | 
			
		||||
 | 
			
		||||
    async def check_permissions_and_switch(self, index, permission_code):
 | 
			
		||||
        """Асинхронно проверяет права и переключает вкладку."""
 | 
			
		||||
        self.preload_permissions_first = False
 | 
			
		||||
        if permission_code in self.permission_cache:
 | 
			
		||||
            self.show_real_content(index)
 | 
			
		||||
            self.switch_tab(index)
 | 
			
		||||
@ -313,17 +353,16 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Иначе делаем запрос
 | 
			
		||||
        access_token = get_current_access_token()
 | 
			
		||||
        access_token = await get_current_access_token()
 | 
			
		||||
        if not access_token:
 | 
			
		||||
            self.show_error_message(localizer.translate("Сессия не найдена. Пожалуйста, войдите снова."))
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        success, data = await get_user_role(access_token)
 | 
			
		||||
        print("data", data)
 | 
			
		||||
 | 
			
		||||
        success, data = await get_user_role(access_token)
 | 
			
		||||
        if success and permission_code in data.get("user_permissions", []):
 | 
			
		||||
            self.permission_cache.add(permission_code)
 | 
			
		||||
            # self.permission_cache.add(permission_code)
 | 
			
		||||
            user_permissions = set(data.get("user_permissions", []))
 | 
			
		||||
            self.permission_cache.update(user_permissions)
 | 
			
		||||
            self.show_real_content(index)
 | 
			
		||||
            self.switch_tab(index)
 | 
			
		||||
        else:
 | 
			
		||||
@ -356,6 +395,7 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
                button.setProperty("selected", button.property("tab_index") == selected_index)
 | 
			
		||||
                button.style().unpolish(button)
 | 
			
		||||
                button.style().polish(button)
 | 
			
		||||
                button.update()
 | 
			
		||||
 | 
			
		||||
    def show_real_content(self, index):
 | 
			
		||||
        if index == 0:
 | 
			
		||||
@ -364,7 +404,7 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
            self.music_label.setText("Контент Музыки")
 | 
			
		||||
 | 
			
		||||
    def show_denied(self, index):
 | 
			
		||||
        denied = QLabel("Доступ запрещен")
 | 
			
		||||
        denied = QLabel(localizer.translate("Доступ запрещен"))
 | 
			
		||||
        denied.setAlignment(Qt.AlignCenter)
 | 
			
		||||
        denied.setStyleSheet("font-size: 18px; color: #8e8e93;")
 | 
			
		||||
 | 
			
		||||
@ -399,17 +439,28 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        """Возвращает QSS стили для компонента в зависимости от темы."""
 | 
			
		||||
        is_dark = theme_manager.is_dark()
 | 
			
		||||
 | 
			
		||||
        # Цветовая палитра
 | 
			
		||||
        bg_color = "#1c1c1e" if is_dark else "white"
 | 
			
		||||
        bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8"
 | 
			
		||||
        bar_border_color = "#3c3c3c" if is_dark else "#e7e7e7"
 | 
			
		||||
        text_color = "#8e8e93" if is_dark else "#888888"
 | 
			
		||||
        title_color = "white" if is_dark else "black"
 | 
			
		||||
        active_color = "#0A84FF"
 | 
			
		||||
        top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5"
 | 
			
		||||
        top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0"
 | 
			
		||||
        hover_color = "#d0d0d0" if not is_dark else "#444444"
 | 
			
		||||
        overlay_color = "rgba(0, 0, 0, 0.5)"
 | 
			
		||||
        # Базовая палитра
 | 
			
		||||
        bg_color        = "#1c1c1e" if is_dark else "white"
 | 
			
		||||
        bar_bg_color    = "#2c2c2e" if is_dark else "#f8f8f8"
 | 
			
		||||
        bar_border_color= "#3c3c3c" if is_dark else "#e7e7e7"
 | 
			
		||||
        text_color      = "#8e8e93" if is_dark else "#777777"
 | 
			
		||||
        title_color     = "white"   if is_dark else "black"
 | 
			
		||||
        top_bar_bg      = "#2c2c2e" if is_dark else "#f5f5f5"
 | 
			
		||||
        top_bar_border  = "#3c3c3c" if is_dark else "#e0e0e0"
 | 
			
		||||
        overlay_color   = "rgba(0, 0, 0, 0.5)"
 | 
			
		||||
 | 
			
		||||
        # Акцент и производные
 | 
			
		||||
        active_hex = "#0A84FF"
 | 
			
		||||
        active_rgb = "10, 132, 255"  # для rgba()
 | 
			
		||||
 | 
			
		||||
        # Hover — нейтральный, чтобы не спорил с выбранным
 | 
			
		||||
        hover_bg    = "rgba(0, 0, 0, 0.06)" if not is_dark else "rgba(255, 255, 255, 0.07)"
 | 
			
		||||
        # Pressed — одинаково в темах
 | 
			
		||||
        pressed_bg  = f"rgba({active_rgb}, 0.36)"
 | 
			
		||||
 | 
			
		||||
        # Selected — РАЗНЫЕ для светлой и тёмной тем
 | 
			
		||||
        selected_bg     = f"rgba({active_rgb}, 0.16)" if not is_dark else f"rgba({active_rgb}, 0.28)"
 | 
			
		||||
        selected_border = f"rgba({active_rgb}, 0.24)" if not is_dark else f"rgba({active_rgb}, 0.42)"
 | 
			
		||||
 | 
			
		||||
        return f"""
 | 
			
		||||
            #content_widget {{
 | 
			
		||||
@ -419,14 +470,13 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
                background-color: {overlay_color};
 | 
			
		||||
            }}
 | 
			
		||||
 | 
			
		||||
            /* Глобальные стили для кнопок */
 | 
			
		||||
            /* Глобально для кнопок */
 | 
			
		||||
            QPushButton {{
 | 
			
		||||
                background: transparent;
 | 
			
		||||
                border: none;
 | 
			
		||||
                outline: none;
 | 
			
		||||
            }}
 | 
			
		||||
            QPushButton:focus,
 | 
			
		||||
            QPushButton:pressed,
 | 
			
		||||
            QPushButton:checked {{
 | 
			
		||||
                background: transparent;
 | 
			
		||||
                border: none;
 | 
			
		||||
@ -464,29 +514,34 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
 | 
			
		||||
            /* Кнопки вкладок */
 | 
			
		||||
            #TabButton {{
 | 
			
		||||
                background: transparent;
 | 
			
		||||
                border: none;
 | 
			
		||||
                outline: none;
 | 
			
		||||
                padding: 5px;
 | 
			
		||||
                padding: 6px 8px;
 | 
			
		||||
                border-radius: 10px;
 | 
			
		||||
                border: 1px solid transparent; /* чтобы при selection не прыгала высота */
 | 
			
		||||
            }}
 | 
			
		||||
            #TabButton:hover {{
 | 
			
		||||
                background-color: {hover_color};
 | 
			
		||||
                border-radius: 6px;
 | 
			
		||||
                background-color: {hover_bg};
 | 
			
		||||
            }}
 | 
			
		||||
            #TabButton:pressed {{
 | 
			
		||||
                background-color: {active_color}22; /* активный цвет с прозрачностью */
 | 
			
		||||
                border-radius: 6px;
 | 
			
		||||
                background-color: {pressed_bg};
 | 
			
		||||
                padding-top: 8px;
 | 
			
		||||
                padding-bottom: 4px;
 | 
			
		||||
            }}
 | 
			
		||||
 | 
			
		||||
            /* Иконка/текст по умолчанию */
 | 
			
		||||
            #TabButton #TabIcon {{ color: {text_color}; }}
 | 
			
		||||
            #TabButton #TabText {{ color: {text_color}; }}
 | 
			
		||||
 | 
			
		||||
            /* ВЫБРАННАЯ вкладка — разные тона для light/dark */
 | 
			
		||||
            #TabButton[selected="true"] {{
 | 
			
		||||
                background-color: {active_color}22;
 | 
			
		||||
                background-color: {selected_bg};
 | 
			
		||||
                border: 1px solid {selected_border};
 | 
			
		||||
            }}
 | 
			
		||||
            #TabButton[selected="true"] #TabIcon,
 | 
			
		||||
            #TabButton[selected="true"] #TabText {{
 | 
			
		||||
                color: {active_color};
 | 
			
		||||
                color: {active_hex};
 | 
			
		||||
                font-weight: 600;
 | 
			
		||||
            }}
 | 
			
		||||
 | 
			
		||||
            #TabIcon, #TabText {{
 | 
			
		||||
                border: none;
 | 
			
		||||
                outline: none;
 | 
			
		||||
@ -521,130 +576,3 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
                );
 | 
			
		||||
            }}
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
    # def get_stylesheet(self):
 | 
			
		||||
    #     """Возвращает QSS стили для компонента в зависимости от темы."""
 | 
			
		||||
    #     is_dark = theme_manager.is_dark()
 | 
			
		||||
 | 
			
		||||
    #     # Цветовая палитра
 | 
			
		||||
    #     bg_color = "#1c1c1e" if is_dark else "white"
 | 
			
		||||
    #     bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8"
 | 
			
		||||
    #     bar_border_color = "#3c3c3c" if is_dark else "#e7e7e7"
 | 
			
		||||
    #     text_color = "#8e8e93" if is_dark else "#888888"
 | 
			
		||||
    #     title_color = "white" if is_dark else "black"
 | 
			
		||||
    #     active_color = "#0A84FF"
 | 
			
		||||
    #     top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5"
 | 
			
		||||
    #     top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0"
 | 
			
		||||
    #     hover_color = "#3a3a3c" if is_dark else "#e0e0e0"
 | 
			
		||||
    #     overlay_color = "rgba(0, 0, 0, 0.5)"
 | 
			
		||||
 | 
			
		||||
    #     return f"""
 | 
			
		||||
    #         #content_widget {{
 | 
			
		||||
    #             background-color: {bg_color};
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #Overlay {{
 | 
			
		||||
    #             background-color: {overlay_color};
 | 
			
		||||
    #         }}
 | 
			
		||||
 | 
			
		||||
    #         /* Глобальные стили для кнопок */
 | 
			
		||||
    #         QPushButton {{
 | 
			
		||||
    #             background: transparent;
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             outline: none;
 | 
			
		||||
    #             transition: background-color 200ms ease, transform 150ms ease;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         QPushButton:focus,
 | 
			
		||||
    #         QPushButton:checked {{
 | 
			
		||||
    #             background: transparent;
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             outline: none;
 | 
			
		||||
    #         }}
 | 
			
		||||
 | 
			
		||||
    #         /* Верхняя панель */
 | 
			
		||||
    #         #TopBar {{
 | 
			
		||||
    #             background-color: {top_bar_bg};
 | 
			
		||||
    #             border-bottom: 1px solid {top_bar_border};
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #TopBar QPushButton {{
 | 
			
		||||
    #             font-size: 22px;
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             padding: 5px;
 | 
			
		||||
    #             color: {title_color};
 | 
			
		||||
    #             background: transparent;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #TitleLabel {{
 | 
			
		||||
    #             font-size: 18px;
 | 
			
		||||
    #             font-weight: bold;
 | 
			
		||||
    #             color: {title_color};
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             outline: none;
 | 
			
		||||
    #             background-color: transparent;
 | 
			
		||||
    #         }}
 | 
			
		||||
 | 
			
		||||
    #         /* Нижняя панель */
 | 
			
		||||
    #         #BottomBar {{
 | 
			
		||||
    #             background-color: {bar_bg_color};
 | 
			
		||||
    #             border-top: 1px solid {bar_border_color};
 | 
			
		||||
    #             padding-top: 5px;
 | 
			
		||||
    #             padding-bottom: 15px;
 | 
			
		||||
    #         }}
 | 
			
		||||
 | 
			
		||||
    #         /* Кнопки вкладок */
 | 
			
		||||
    #         #TabButton {{
 | 
			
		||||
    #             border-radius: 6px;
 | 
			
		||||
    #             padding: 6px;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         /* Hover (не выбранная вкладка) */
 | 
			
		||||
    #         #TabButton:hover[selected="false"] {{
 | 
			
		||||
    #             background-color: {hover_color};
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         /* Pressed (удержание) */
 | 
			
		||||
    #         #TabButton:pressed {{
 | 
			
		||||
    #             background-color: {active_color}44;
 | 
			
		||||
    #             transform: scale(0.95);
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         /* Selected */
 | 
			
		||||
    #         #TabButton[selected="true"] {{
 | 
			
		||||
    #             background-color: {active_color}22;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #TabButton[selected="true"] #TabIcon,
 | 
			
		||||
    #         #TabButton[selected="true"] #TabText {{
 | 
			
		||||
    #             color: {active_color};
 | 
			
		||||
    #             font-weight: 600;
 | 
			
		||||
    #         }}
 | 
			
		||||
 | 
			
		||||
    #         #TabIcon, #TabText {{
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             outline: none;
 | 
			
		||||
    #             background-color: transparent;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #TabIcon {{ font-size: 22px; color: {text_color}; }}
 | 
			
		||||
    #         #TabText {{ font-size: 12px; color: {text_color}; }}
 | 
			
		||||
 | 
			
		||||
    #         /* Центральная кнопка "Создать" */
 | 
			
		||||
    #         #CreateButton {{
 | 
			
		||||
    #             color: white;
 | 
			
		||||
    #             font-size: 30px;
 | 
			
		||||
    #             font-weight: 300;
 | 
			
		||||
    #             border: none;
 | 
			
		||||
    #             border-radius: 28px;
 | 
			
		||||
    #             background-color: qlineargradient(
 | 
			
		||||
    #                 x1: 0, y1: 0, x2: 0, y2: 1,
 | 
			
		||||
    #                 stop: 0 #007AFF, stop: 1 #0056b3
 | 
			
		||||
    #             );
 | 
			
		||||
    #             margin-bottom: 20px;
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #CreateButton:hover {{
 | 
			
		||||
    #             background-color: qlineargradient(
 | 
			
		||||
    #                 x1: 0, y1: 0, x2: 0, y2: 1,
 | 
			
		||||
    #                 stop: 0 #0088FF, stop: 1 #0066c3
 | 
			
		||||
    #             );
 | 
			
		||||
    #         }}
 | 
			
		||||
    #         #CreateButton:pressed {{
 | 
			
		||||
    #             background-color: qlineargradient(
 | 
			
		||||
    #                 x1: 0, y1: 0, x2: 0, y2: 1,
 | 
			
		||||
    #                 stop: 0 #0056b3, stop: 1 #004493
 | 
			
		||||
    #             );
 | 
			
		||||
    #             transform: scale(0.95);
 | 
			
		||||
    #         }}
 | 
			
		||||
    #     """
 | 
			
		||||
 | 
			
		||||
@ -8,3 +8,5 @@ common-lib @ git+https://githlam.com/messenger/common_lib.git@main
 | 
			
		||||
httpx[http2]==0.28.1
 | 
			
		||||
asyncio==4.0.0
 | 
			
		||||
qasync==0.28.0
 | 
			
		||||
logging==0.4.9.6
 | 
			
		||||
aiosqlite==0.21.0
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user