diff --git a/app/core/theme.py b/app/core/theme.py index c2286cc..dc1be82 100644 --- a/app/core/theme.py +++ b/app/core/theme.py @@ -20,6 +20,33 @@ class ThemeManager(QObject): super().__init__() self.settings = QSettings("yobble_messenger", "Theme") self.theme = self.settings.value("theme", "dark") + self.color_palette = { + "dark": { + "background": "#2a2a2a", + "primary": "#3c3c3c", + "secondary": "#4a4a4a", + "text": "#ffffff", + "text_secondary": "#aaaaaa", + "accent": "#5a9bcf", + "border": "#4a4a4a", + "hover": "#4a4a4a", + "selected": "#5a9bcf", + }, + "light": { + "background": "#ffffff", + "primary": "#f5f5f5", + "secondary": "#e0e0e0", + "text": "#000000", + "text_secondary": "#555555", + "accent": "#0078d7", + "border": "#e0e0e0", + "hover": "#e9e9e9", + "selected": "#dcdcdc", + } + } + + def get_current_palette(self): + return self.color_palette[self.theme] def get_theme(self): return self.theme diff --git a/app/ui/views/chat_list_view.py b/app/ui/views/chat_list_view.py index 896bc7c..d9485de 100644 --- a/app/ui/views/chat_list_view.py +++ b/app/ui/views/chat_list_view.py @@ -3,65 +3,76 @@ from PySide6.QtCore import Qt, QSize from typing import List from app.core.models.chat_models import PrivateChatListItem from app.ui.widgets.chat_list_item_widget import ChatListItemWidget +from app.core.theme import theme_manager from datetime import datetime class ChatListView(QWidget): def __init__(self): super().__init__() self.init_ui() + self.update_theme() + theme_manager.theme_changed.connect(self.update_theme) def init_ui(self): """Инициализирует пользовательский интерфейс.""" layout = QVBoxLayout(self) - layout.setContentsMargins(10, 10, 10, 10) + layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.chat_list = QListWidget() - self.chat_list.setStyleSheet(""" - QListWidget { - background-color: #ffffff; - border: none; - padding: 5px; - spacing: 8px; - outline: 0; - } - QListWidget::item { - background-color: #f5f5f5; - border-radius: 10px; - padding: 5px; - } - QListWidget::item:hover { - background-color: #e9e9e9; - } - QListWidget::item:selected { - background-color: #dcdcdc; - color: #000000; - } - QScrollBar:vertical { - border: none; - background: #f5f5f5; - width: 8px; - margin: 0px 0px 0px 0px; - } - QScrollBar::handle:vertical { - background: #cccccc; - min-height: 20px; - border-radius: 4px; - } - QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { - height: 0px; - } - """) + self.chat_list.setSpacing(2) layout.addWidget(self.chat_list) # Изначальное состояние self.show_placeholder_message("Загрузка чатов...") + def update_theme(self): + """Обновляет стили в соответствии с темой.""" + palette = theme_manager.get_current_palette() + self.chat_list.setStyleSheet(f""" + QListWidget {{ + background-color: {palette['primary']}; + border: none; + padding: 5px; + outline: 0; + }} + QListWidget::item {{ + border-radius: 5px; + }} + QListWidget::item:hover {{ + background-color: {palette['hover']}; + }} + QListWidget::item:selected {{ + background-color: {palette['selected']}; + }} + QScrollBar:vertical {{ + border: none; + background: {palette['primary']}; + width: 8px; + margin: 0px 0px 0px 0px; + }} + QScrollBar::handle:vertical {{ + background: {palette['secondary']}; + min-height: 20px; + border-radius: 4px; + }} + QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ + height: 0px; + }} + """) + # Обновляем существующие элементы + for i in range(self.chat_list.count()): + item = self.chat_list.item(i) + widget = self.chat_list.itemWidget(item) + if isinstance(widget, ChatListItemWidget): + widget.update_theme() + def show_placeholder_message(self, text): """Очищает список и показывает одно сообщение (например, "Загрузка..." или "Чатов нет").""" self.chat_list.clear() item = QListWidgetItem(text) item.setTextAlignment(Qt.AlignCenter) + # Убираем фон у элемента-заглушки item.setBackground(Qt.transparent) self.chat_list.addItem(item) @@ -74,6 +85,9 @@ class ChatListView(QWidget): if not chat_items: self.show_placeholder_message("У вас пока нет чатов") return + + # Сортируем чаты по времени последнего сообщения + chat_items.sort(key=lambda x: x.last_message.created_at if x.last_message else datetime.min, reverse=True) for chat in chat_items: # Определяем имя собеседника @@ -87,15 +101,16 @@ class ChatListView(QWidget): # Получаем текст и время последнего сообщения if chat.last_message and chat.last_message.content: last_msg = chat.last_message.content - - # Преобразуем время timestamp = chat.last_message.created_at.strftime('%H:%M') else: last_msg = "Нет сообщений" timestamp = "" + # TODO: Заменить на реальное количество непрочитанных сообщений + unread_count = 2 + # Создаем кастомный виджет - item_widget = ChatListItemWidget(companion_name, last_msg, timestamp) + item_widget = ChatListItemWidget(companion_name, last_msg, timestamp, unread_count=unread_count) # Создаем элемент списка и устанавливаем для него наш виджет list_item = QListWidgetItem(self.chat_list) diff --git a/app/ui/widgets/chat_list_item_widget.py b/app/ui/widgets/chat_list_item_widget.py index 4725f51..e39e354 100644 --- a/app/ui/widgets/chat_list_item_widget.py +++ b/app/ui/widgets/chat_list_item_widget.py @@ -1,16 +1,18 @@ from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel from PySide6.QtGui import QPixmap, QPainter, QColor, QBrush from PySide6.QtCore import Qt +from app.core.theme import theme_manager class ChatListItemWidget(QWidget): - def __init__(self, companion_name, last_message, timestamp, avatar_path=None): + def __init__(self, companion_name, last_message, timestamp, avatar_path=None, unread_count=0): super().__init__() - self.init_ui(companion_name, last_message, timestamp, avatar_path) + self.init_ui(companion_name, last_message, timestamp, avatar_path, unread_count) + self.update_theme() - def init_ui(self, companion_name, last_message, timestamp, avatar_path): + def init_ui(self, companion_name, last_message, timestamp, avatar_path, unread_count): """Инициализирует пользовательский интерфейс виджета.""" main_layout = QHBoxLayout(self) - main_layout.setContentsMargins(10, 10, 10, 10) + main_layout.setContentsMargins(10, 5, 10, 5) main_layout.setSpacing(10) # Аватар @@ -18,31 +20,52 @@ class ChatListItemWidget(QWidget): self.set_avatar(avatar_path) main_layout.addWidget(self.avatar_label) - # Информация о чате + # Центральная часть: имя и последнее сообщение info_layout = QVBoxLayout() - info_layout.setSpacing(0) - - # Верхняя строка: имя и время - top_line_layout = QHBoxLayout() + info_layout.setSpacing(2) self.name_label = QLabel(companion_name) - self.name_label.setStyleSheet("font-weight: bold;") - - self.timestamp_label = QLabel(timestamp) - self.timestamp_label.setStyleSheet("color: grey; font-size: 9px;") - - top_line_layout.addWidget(self.name_label) - top_line_layout.addStretch() - top_line_layout.addWidget(self.timestamp_label) - self.last_message_label = QLabel(last_message) - self.last_message_label.setStyleSheet("color: grey;") - - info_layout.addLayout(top_line_layout) + + info_layout.addWidget(self.name_label) info_layout.addWidget(self.last_message_label) + info_layout.addStretch() + + # Правая часть: время и количество непрочитанных + meta_layout = QVBoxLayout() + meta_layout.setSpacing(2) + meta_layout.setAlignment(Qt.AlignRight) + + self.timestamp_label = QLabel(timestamp) + self.unread_label = QLabel(str(unread_count) if unread_count > 0 else "") + self.unread_label.setAlignment(Qt.AlignCenter) + + meta_layout.addWidget(self.timestamp_label) + meta_layout.addWidget(self.unread_label) + meta_layout.addStretch() main_layout.addLayout(info_layout) - main_layout.addStretch() + main_layout.addLayout(meta_layout) + + self.update_theme() + + def update_theme(self): + """Обновляет стили виджета в соответствии с текущей темой.""" + palette = theme_manager.get_current_palette() + + self.name_label.setStyleSheet(f"font-weight: bold; font-size: 11pt; color: {palette['text']};") + self.last_message_label.setStyleSheet(f"font-size: 9pt; color: {palette['text_secondary']};") + self.timestamp_label.setStyleSheet(f"font-size: 8pt; color: {palette['text_secondary']};") + + unread_bg = palette['accent'] + unread_text = "#ffffff" + self.unread_label.setStyleSheet( + f"font-size: 8pt; font-weight: bold; color: {unread_text}; " + f"background-color: {unread_bg}; border-radius: 8px; " + f"min-width: 16px; min-height: 16px; padding: 0 4px;" + ) + self.unread_label.setVisible(self.unread_label.text() != "") + def set_avatar(self, image_path): """Устанавливает аватар. Если путь не указан, создает заглушку."""