import asyncio from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QStackedWidget, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect, QGraphicsOpacityEffect, QMessageBox ) from PySide6.QtCore import Qt, QSize, QPropertyAnimation, QEasingCurve, Property from PySide6.QtGui import QIcon, QColor from app.core.theme import theme_manager from app.ui.views.side_menu_view import SideMenuView from app.core.services.auth_service import get_user_role from app.core.database import get_current_access_token from app.core.localizer import localizer class YobbleHomeView(QWidget): def __init__(self, username: str): super().__init__() self.username = username self.setWindowTitle(f"Yobble Home - {username}") self.setMinimumSize(360, 640) self.permission_cache = set() # --- Основной макет --- # Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом # Но на самом деле меню будет поверх контента main_layout = QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) # --- Виджет для основного контента --- self.content_widget = QWidget() content_layout = QVBoxLayout(self.content_widget) content_layout.setContentsMargins(0, 0, 0, 0) content_layout.setSpacing(0) # 1. Верхняя панель self.top_bar = self.create_top_bar() content_layout.addWidget(self.top_bar) # 2. Центральная область контента self.content_stack = QStackedWidget() self.setup_content_pages() content_layout.addWidget(self.content_stack, 1) # 3. Нижняя панель навигации self.bottom_bar = self.create_bottom_bar() self.content_stack.setCurrentIndex(2) content_layout.addWidget(self.bottom_bar) main_layout.addWidget(self.content_widget) # --- Боковое меню и оверлей --- self.setup_side_menu() self.update_styles() theme_manager.theme_changed.connect(self.update_styles) def setup_side_menu(self): """Настраивает боковое меню и оверлей.""" # Оверлей для затемнения контента self.overlay = QFrame(self) self.overlay.setObjectName("Overlay") self.overlay.hide() self.overlay.mousePressEvent = lambda event: self.toggle_side_menu() # Эффект прозрачности для оверлея self.opacity_effect = QGraphicsOpacityEffect(self.overlay) self.overlay.setGraphicsEffect(self.opacity_effect) # Боковое меню self.side_menu = SideMenuView(self) self.side_menu.move(-self.side_menu.width(), 0) # Изначально скрыто за экраном # Анимация для меню self.menu_animation = QPropertyAnimation(self.side_menu, b"pos") self.menu_animation.setEasingCurve(QEasingCurve.InOutCubic) self.menu_animation.setDuration(300) # Анимация для оверлея self.opacity_animation = QPropertyAnimation(self.opacity_effect, b"opacity") self.opacity_animation.setDuration(300) # --- Состояние анимации --- self.is_menu_closing = False self.opacity_animation.finished.connect(self._on_menu_animation_finished) def _on_menu_animation_finished(self): """Срабатывает после завершения анимации оверлея.""" if self.is_menu_closing: self.overlay.hide() def toggle_side_menu(self): """Показывает или скрывает боковое меню с анимацией.""" # Останавливаем текущие анимации, чтобы избежать конфликтов self.menu_animation.stop() self.opacity_animation.stop() is_hidden = self.side_menu.pos().x() < 0 if is_hidden: # --- Открытие --- self.is_menu_closing = False self.overlay.show() self.menu_animation.setStartValue(self.side_menu.pos()) self.menu_animation.setEndValue(self.side_menu.pos().__class__(0, 0)) self.opacity_animation.setStartValue(self.opacity_effect.opacity()) self.opacity_animation.setEndValue(0.5) else: # --- Закрытие --- self.is_menu_closing = True self.menu_animation.setStartValue(self.side_menu.pos()) self.menu_animation.setEndValue(self.side_menu.pos().__class__(-self.side_menu.width(), 0)) self.opacity_animation.setStartValue(self.opacity_effect.opacity()) self.opacity_animation.setEndValue(0) self.menu_animation.start() self.opacity_animation.start() def resizeEvent(self, event): """Обновляет геометрию оверлея и меню при изменении размера окна.""" super().resizeEvent(event) self.overlay.setGeometry(self.rect()) if self.side_menu.pos().x() < 0: self.side_menu.move(-self.side_menu.width(), 0) self.side_menu.setFixedHeight(self.height()) def update_styles(self): """Обновляет стили компонента при смене темы.""" self.setStyleSheet(self.get_stylesheet()) def create_top_bar(self): """Создает верхнюю панель с меню и заголовком.""" top_bar_widget = QWidget() top_bar_widget.setObjectName("TopBar") top_bar_layout = QHBoxLayout(top_bar_widget) top_bar_layout.setContentsMargins(10, 5, 10, 5) self.burger_menu_button = QPushButton("☰") self.burger_menu_button.setObjectName("BurgerMenuButton") self.burger_menu_button.setFocusPolicy(Qt.NoFocus) self.burger_menu_button.setCursor(Qt.PointingHandCursor) self.burger_menu_button.clicked.connect(self.toggle_side_menu) # Подключаем сигнал top_bar_layout.addWidget(self.burger_menu_button) self.title_label = QLabel("Чаты") self.title_label.setObjectName("TitleLabel") top_bar_layout.addWidget(self.title_label) top_bar_layout.addStretch() # Новые кнопки справа self.search_button = QPushButton("🔍") self.search_button.setObjectName("SearchButton") self.search_button.setFocusPolicy(Qt.NoFocus) self.search_button.setCursor(Qt.PointingHandCursor) top_bar_layout.addWidget(self.search_button) self.notification_button = QPushButton("🔔") self.notification_button.setObjectName("NotificationButton") self.notification_button.setFocusPolicy(Qt.NoFocus) self.notification_button.setCursor(Qt.PointingHandCursor) top_bar_layout.addWidget(self.notification_button) return top_bar_widget def create_bottom_bar(self): """Создает нижнюю панель навигации в стиле SwiftUI.""" bottom_bar_widget = QWidget() bottom_bar_widget.setObjectName("BottomBar") bottom_bar_layout = QHBoxLayout(bottom_bar_widget) bottom_bar_layout.setContentsMargins(10, 0, 10, 0) bottom_bar_layout.setSpacing(10) btn_feed = self.create_tab_button("☰", "Лента", 0) btn_music = self.create_tab_button("🎵", "Музыка", 1) btn_create = self.create_create_button() btn_chats = self.create_tab_button("💬", "Чаты", 2) btn_profile = self.create_tab_button("👤", "Лицо", 3) bottom_bar_layout.addWidget(btn_feed) bottom_bar_layout.addWidget(btn_music) bottom_bar_layout.addWidget(btn_create) bottom_bar_layout.addWidget(btn_chats) bottom_bar_layout.addWidget(btn_profile) for btn in [btn_feed, btn_music, btn_chats, btn_profile]: btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) btn.setFocusPolicy(Qt.NoFocus) btn_create.setFocusPolicy(Qt.NoFocus) self.update_tab_selection(2) return bottom_bar_widget def create_tab_button(self, icon_text, text, index): button = QPushButton() button.setObjectName("TabButton") button.setCursor(Qt.PointingHandCursor) button.setFocusPolicy(Qt.NoFocus) layout = QVBoxLayout(button) layout.setContentsMargins(0, 5, 0, 5) layout.setSpacing(4) icon_label = QLabel(icon_text) icon_label.setAlignment(Qt.AlignCenter) icon_label.setObjectName("TabIcon") text_label = QLabel(text) text_label.setAlignment(Qt.AlignCenter) text_label.setObjectName("TabText") layout.addWidget(icon_label) layout.addWidget(text_label) button.setProperty("tab_index", index) button.clicked.connect(lambda: self.on_tab_button_clicked(index)) return button def create_create_button(self): button = QPushButton("+") button.setObjectName("CreateButton") button.setFixedSize(56, 56) button.setCursor(Qt.PointingHandCursor) button.setFocusPolicy(Qt.NoFocus) shadow = QGraphicsDropShadowEffect(self) shadow.setBlurRadius(18) shadow.setColor(QColor(0, 0, 0, 100)) shadow.setOffset(0, 3) button.setGraphicsEffect(shadow) return button def setup_content_pages(self): self.content_stack.addWidget(QLabel("Контент Ленты")) self.content_stack.addWidget(QLabel("Контент Музыки")) self.content_stack.addWidget(QLabel("Контент Чатов")) self.content_stack.addWidget(QLabel("Контент Профиля")) def on_tab_button_clicked(self, index): """Обрабатывает нажатие на кнопку вкладки, проверяя права доступа.""" required_permissions = { 0: "post.access", # Лента 1: "music.access" # Музыка } if index in required_permissions: # Запускаем асинхронную проверку в существующем цикле событий asyncio.create_task(self.check_permissions_and_switch(index, required_permissions[index])) else: # Для вкладок без специальных прав доступа self.switch_tab(index) async def check_permissions_and_switch(self, index, permission_code): """Асинхронно проверяет права и переключает вкладку.""" if permission_code in self.permission_cache: self.switch_tab(index) return access_token = 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) if success and permission_code in data.get("user_permissions", []): self.permission_cache.add(permission_code) self.switch_tab(index) else: error_message = data if not success else localizer.translate("У вас нет прав для доступа к этому разделу.") self.show_error_message(error_message) def switch_tab(self, index): """Переключает на указанную вкладку.""" self.content_stack.setCurrentIndex(index) self.update_tab_selection(index) titles = ["Лента", "Музыка", "Чаты", "Лицо"] self.title_label.setText(titles[index]) def show_error_message(self, message): """Показывает диалоговое окно с сообщением об ошибке.""" msg_box = QMessageBox(self) msg_box.setIcon(QMessageBox.Warning) msg_box.setText(message) msg_box.setWindowTitle(localizer.translate("Ошибка доступа")) msg_box.setStandardButtons(QMessageBox.Ok) msg_box.exec() def update_tab_selection(self, selected_index): if not hasattr(self, 'bottom_bar'): return for button in self.bottom_bar.findChildren(QPushButton): is_tab_button = button.property("tab_index") is not None if is_tab_button: button.setProperty("selected", button.property("tab_index") == selected_index) button.style().unpolish(button) button.style().polish(button) 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 = "#d0d0d0" if not is_dark else "#444444" 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; }} QPushButton:focus, QPushButton:pressed, 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 {{ background: transparent; border: none; outline: none; padding: 5px; }} #TabButton:hover {{ background-color: {hover_color}; border-radius: 6px; }} #TabButton #TabIcon {{ color: {text_color}; }} #TabButton #TabText {{ color: {text_color}; }} #TabButton[selected="true"] #TabIcon, #TabButton[selected="true"] #TabText {{ color: {active_color}; }} #TabIcon, #TabText {{ border: none; outline: none; background-color: transparent; }} #TabIcon {{ font-size: 22px; }} #TabText {{ font-size: 12px; }} /* Центральная кнопка "Создать" */ #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 ); }} """