add chat list
This commit is contained in:
		
							parent
							
								
									99029541d3
								
							
						
					
					
						commit
						21e8bb0ad0
					
				@ -8,11 +8,15 @@ from threading import Thread
 | 
			
		||||
import time
 | 
			
		||||
import asyncio
 | 
			
		||||
from app.core.database import get_last_login, get_session, set_last_login
 | 
			
		||||
# Импортируем сервис чатов
 | 
			
		||||
from app.core.services.chat_service import get_private_chats
 | 
			
		||||
 | 
			
		||||
class MainController(QStackedWidget):
 | 
			
		||||
    # Сигнал для показа уведомлений из любого потока
 | 
			
		||||
    #      (message, is_error)
 | 
			
		||||
    notification_requested = Signal(str, bool)
 | 
			
		||||
    # Сигнал для передачи загруженных данных чата в основной поток
 | 
			
		||||
    chats_loaded = Signal(object)
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
@ -49,8 +53,9 @@ class MainController(QStackedWidget):
 | 
			
		||||
 | 
			
		||||
        self.yobble_home_view = YobbleHomeView(username=username)
 | 
			
		||||
        
 | 
			
		||||
        # Подключаем сигнал к слоту в YobbleHomeView
 | 
			
		||||
        # Подключаем сигналы к слотам в YobbleHomeView
 | 
			
		||||
        self.notification_requested.connect(self.yobble_home_view.show_notification)
 | 
			
		||||
        self.chats_loaded.connect(self.yobble_home_view.update_chat_list)
 | 
			
		||||
        
 | 
			
		||||
        self.addWidget(self.yobble_home_view)
 | 
			
		||||
        self.setCurrentWidget(self.yobble_home_view)
 | 
			
		||||
@ -72,8 +77,32 @@ class MainController(QStackedWidget):
 | 
			
		||||
                # asyncio.run() выполняет async функцию и возвращает её результат
 | 
			
		||||
                asyncio.run(self.yobble_home_view.preload_permissions())
 | 
			
		||||
 | 
			
		||||
                # Загружаем список чатов
 | 
			
		||||
                print("[Sync] Загружаем список чатов...")
 | 
			
		||||
                asyncio.run(self.load_chats(username))
 | 
			
		||||
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                error_message = f"Ошибка предзагрузки: {e}"
 | 
			
		||||
                print(f"[Sync] {error_message}")
 | 
			
		||||
                # Отправляем сигнал вместо прямого вызова
 | 
			
		||||
                self.notification_requested.emit(error_message, True)
 | 
			
		||||
 | 
			
		||||
    async def load_chats(self, username: str):
 | 
			
		||||
        """
 | 
			
		||||
        Загружает список чатов для пользователя.
 | 
			
		||||
        """
 | 
			
		||||
        session = get_session(username)
 | 
			
		||||
        # print("debug session", session)
 | 
			
		||||
        # if not session or 'access_token' not in session:
 | 
			
		||||
        #     self.notification_requested.emit("Сессия не найдена, не могу загрузить чаты.", True)
 | 
			
		||||
        #     return
 | 
			
		||||
 | 
			
		||||
        token = session['access_token']
 | 
			
		||||
        success, data = await get_private_chats(token=token, offset=0, limit=50)
 | 
			
		||||
 | 
			
		||||
        if success:
 | 
			
		||||
            # Отправляем данные в основной поток через сигнал
 | 
			
		||||
            self.chats_loaded.emit(data)
 | 
			
		||||
        else:
 | 
			
		||||
            # Отправляем ошибку в основной поток через сигнал
 | 
			
		||||
            self.notification_requested.emit(f"Не удалось загрузить чаты: {data}", True)
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,47 @@
 | 
			
		||||
from pydantic import BaseModel, Field
 | 
			
		||||
from typing import Optional, Dict, Any, List, Literal
 | 
			
		||||
from uuid import UUID
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from pydantic import BaseModel, Field
 | 
			
		||||
from typing import Optional, Literal, List, Any, Dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MessageForward(BaseModel):
 | 
			
		||||
    forward_type: Optional[Literal["chat_private_messages", "chat_group_messages",
 | 
			
		||||
                                   "chat_public_messages", "reply"]] = Field(None, description="Тип пересылаемого контента")
 | 
			
		||||
    forward_sender_id: Optional[UUID] = Field(None, description="ID чата, откуда переслано сообщение")
 | 
			
		||||
    forward_message_id: Optional[int] = Field(None, description="Данные внутренний ид сообщения в forward_chat")
 | 
			
		||||
    forward_chat_data: Optional[Any] = Field(default=None, description="Данные о чате пересылаемом (беседы и паблики)")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MessageItem(BaseModel):
 | 
			
		||||
    message_id: int = Field(..., description="внутренний ID сообщения")
 | 
			
		||||
    message_type: List[Literal["text", "media", "circle", "voice", "system", "forward",
 | 
			
		||||
                               "reply", "poll"]] = Field(..., alias="message_type", description="Типы сообщения")
 | 
			
		||||
 | 
			
		||||
    forward_metadata: Optional[MessageForward]
 | 
			
		||||
 | 
			
		||||
    chat_id: UUID = Field(..., description="Чат ID")
 | 
			
		||||
    sender_id: UUID = Field(..., description="Кто отправил")
 | 
			
		||||
    sender_data: Optional[Any] = Field(default=None, description="Данные о пользователе")
 | 
			
		||||
    content: Optional[str] = Field(None, description="Текст сообщения")
 | 
			
		||||
    media_link: Optional[Any] = Field(None, description="Ссылка на медиа (заглушка)")
 | 
			
		||||
    is_viewed: bool = Field(..., description="Флаг просмотра")
 | 
			
		||||
    created_at: datetime = Field(..., description="Дата и время создания сообщения")
 | 
			
		||||
    updated_at: Optional[datetime] = Field(None, description="Дата и время обновления сообщения")
 | 
			
		||||
 | 
			
		||||
class LastMessage(BaseModel):
 | 
			
		||||
    message_id: int
 | 
			
		||||
    message_type: List[Literal["text", "media", "circle", "voice", "system", "forward", "reply", "poll"]]
 | 
			
		||||
    context: str
 | 
			
		||||
    created_at: datetime
 | 
			
		||||
 | 
			
		||||
class PrivateChatListItem(BaseModel):
 | 
			
		||||
    chat_name: Optional[str]
 | 
			
		||||
    chat_type: Literal["self", "private"]
 | 
			
		||||
    chat_id: UUID
 | 
			
		||||
    chat_data: Optional[Dict[str, Any]] = None
 | 
			
		||||
    companion_id: Optional[UUID] = None
 | 
			
		||||
    companion_data: Optional[Dict[str, Any]] = None  # Типизируй как нужно
 | 
			
		||||
    last_message: Optional[LastMessage] = None
 | 
			
		||||
    created_at: datetime
 | 
			
		||||
    chat_id: UUID = Field(..., description="ID чата")
 | 
			
		||||
    chat_type: Literal["self", "private"] = Field(..., description="Тип чата")
 | 
			
		||||
    chat_data: Optional[Dict[str, Any]] = Field(default=None, description="Данные о чате")
 | 
			
		||||
    last_message: Optional[MessageItem] = Field(None, description="Последнее сообщение в чате")
 | 
			
		||||
    created_at: datetime = Field(..., description="Дата создания чата")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrivateChatListData(BaseModel):
 | 
			
		||||
    items: List[PrivateChatListItem]
 | 
			
		||||
    has_more: bool
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrivateChatListResponse(BaseModel):
 | 
			
		||||
    status: str
 | 
			
		||||
    data: PrivateChatListData
 | 
			
		||||
							
								
								
									
										48
									
								
								app/core/services/chat_service.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/core/services/chat_service.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import httpx
 | 
			
		||||
from app.core import config
 | 
			
		||||
from app.core.localizer import localizer
 | 
			
		||||
from app.core.models.chat_models import PrivateChatListResponse, PrivateChatListData
 | 
			
		||||
 | 
			
		||||
async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
 | 
			
		||||
    """
 | 
			
		||||
    Получает список приватных чатов пользователя.
 | 
			
		||||
 | 
			
		||||
    :param token: Токен доступа пользователя
 | 
			
		||||
    :param offset: Смещение для пагинации
 | 
			
		||||
    :param limit: Количество чатов для загрузки
 | 
			
		||||
    :return: Кортеж (успех: bool, данные: PrivateChatListData | str)
 | 
			
		||||
    """
 | 
			
		||||
    # TODO: Добавить логику обновления токена, как в auth_service.py
 | 
			
		||||
    url = f"{config.BASE_URL}/v1/chat/private/list"
 | 
			
		||||
    headers = {"Authorization": f"Bearer {token}"}
 | 
			
		||||
    params = {"offset": offset, "limit": limit}
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        async with httpx.AsyncClient(http2=True) as client:
 | 
			
		||||
            response = await client.get(url, headers=headers, params=params)
 | 
			
		||||
            print("response.status_code", response.status_code)
 | 
			
		||||
 | 
			
		||||
            if response.status_code == 200:
 | 
			
		||||
                data = response.json()
 | 
			
		||||
                if data.get("status") == "fine":
 | 
			
		||||
                    # Используем Pydantic модель для парсинга ответа
 | 
			
		||||
                    response_model = PrivateChatListResponse(**data)
 | 
			
		||||
                    print("response_model.data", response_model.data)
 | 
			
		||||
                    return True, response_model.data
 | 
			
		||||
                else:
 | 
			
		||||
                    return False, data.get("detail", localizer.translate("Неизвестная ошибка ответа"))
 | 
			
		||||
            
 | 
			
		||||
            elif response.status_code in [401, 403]:
 | 
			
		||||
                error_data = response.json()
 | 
			
		||||
                return False, error_data.get("detail", localizer.translate("Ошибка аутентификации или авторизации"))
 | 
			
		||||
 | 
			
		||||
            elif response.status_code == 422:
 | 
			
		||||
                return False, localizer.translate("Некорректные параметры запроса")
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
 | 
			
		||||
 | 
			
		||||
    except httpx.RequestError as e:
 | 
			
		||||
        return False, f"{localizer.translate('Ошибка сети')}: {e}"
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        return False, f"{localizer.translate('Произошла ошибка')}: {e}"
 | 
			
		||||
@ -1,35 +1,59 @@
 | 
			
		||||
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel, QListWidgetItem
 | 
			
		||||
from PySide6.QtCore import Qt
 | 
			
		||||
from typing import List
 | 
			
		||||
from app.core.models.chat_models import PrivateChatListItem
 | 
			
		||||
 | 
			
		||||
class ChatListView(QWidget):
 | 
			
		||||
    def __init__(self, username, chat_items: list[PrivateChatListItem]):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.setWindowTitle(f"Чаты — {username}")
 | 
			
		||||
        self.setMinimumSize(400, 500)
 | 
			
		||||
 | 
			
		||||
        self.chat_items = chat_items
 | 
			
		||||
        self.init_ui()
 | 
			
		||||
 | 
			
		||||
    def init_ui(self):
 | 
			
		||||
        layout = QVBoxLayout()
 | 
			
		||||
 | 
			
		||||
        self.label = QLabel("Список чатов:")
 | 
			
		||||
        layout.addWidget(self.label)
 | 
			
		||||
        """Инициализирует пользовательский интерфейс."""
 | 
			
		||||
        layout = QVBoxLayout(self)
 | 
			
		||||
        layout.setContentsMargins(0, 0, 0, 0)
 | 
			
		||||
 | 
			
		||||
        self.chat_list = QListWidget()
 | 
			
		||||
        self.render_chat_items()
 | 
			
		||||
        self.chat_list.setStyleSheet("QListWidget { border: none; }")
 | 
			
		||||
        layout.addWidget(self.chat_list)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
        # Изначальное состояние
 | 
			
		||||
        self.show_placeholder_message("Загрузка чатов...")
 | 
			
		||||
 | 
			
		||||
    def render_chat_items(self):
 | 
			
		||||
    def show_placeholder_message(self, text):
 | 
			
		||||
        """Очищает список и показывает одно сообщение (например, "Загрузка..." или "Чатов нет")."""
 | 
			
		||||
        self.chat_list.clear()
 | 
			
		||||
        for chat in self.chat_items:
 | 
			
		||||
            companion_name = chat.companion_data.get("username", "Без имени") if chat.companion_data else "Неизвестный"
 | 
			
		||||
            last_msg = chat.last_message.context if chat.last_message else "Нет сообщений"
 | 
			
		||||
            item_text = f"{companion_name} — {last_msg}"
 | 
			
		||||
            self.chat_list.addItem(QListWidgetItem(item_text))
 | 
			
		||||
        item = QListWidgetItem(text)
 | 
			
		||||
        item.setTextAlignment(Qt.AlignCenter)
 | 
			
		||||
        self.chat_list.addItem(item)
 | 
			
		||||
 | 
			
		||||
    def update_chat_items(self, new_items: list[PrivateChatListItem]):
 | 
			
		||||
        self.chat_items = new_items
 | 
			
		||||
        self.render_chat_items()
 | 
			
		||||
    def populate_chats(self, chat_items: List[PrivateChatListItem]):
 | 
			
		||||
        """
 | 
			
		||||
        Заполняет список чатов данными, полученными от сервера.
 | 
			
		||||
        """
 | 
			
		||||
        self.chat_list.clear()
 | 
			
		||||
 | 
			
		||||
        if not chat_items:
 | 
			
		||||
            self.show_placeholder_message("У вас пока нет чатов")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for chat in chat_items:
 | 
			
		||||
            # Определяем имя собеседника
 | 
			
		||||
            if chat.chat_type == "self":
 | 
			
		||||
                companion_name = "Избранное"
 | 
			
		||||
            elif chat.chat_data and 'login' in chat.chat_data:
 | 
			
		||||
                companion_name = chat.chat_data['login']
 | 
			
		||||
            else:
 | 
			
		||||
                companion_name = "Неизвестный"
 | 
			
		||||
 | 
			
		||||
            # Получаем текст последнего сообщения
 | 
			
		||||
            if chat.last_message and chat.last_message.content:
 | 
			
		||||
                last_msg = chat.last_message.content
 | 
			
		||||
            else:
 | 
			
		||||
                last_msg = "Нет сообщений"
 | 
			
		||||
            
 | 
			
		||||
            # Создаем кастомный виджет для элемента списка (можно будет улучшить)
 | 
			
		||||
            # Пока просто текстом
 | 
			
		||||
            item_text = f"{companion_name}\n{last_msg}"
 | 
			
		||||
            list_item = QListWidgetItem(item_text)
 | 
			
		||||
            self.chat_list.addItem(list_item)
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ from PySide6.QtGui import QColor
 | 
			
		||||
from app.core.theme import theme_manager
 | 
			
		||||
from app.core.dialogs import show_themed_messagebox
 | 
			
		||||
from app.ui.views.side_menu_view import SideMenuView
 | 
			
		||||
from app.ui.views.chat_list_view import ChatListView
 | 
			
		||||
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
 | 
			
		||||
@ -366,11 +367,21 @@ class YobbleHomeView(QWidget):
 | 
			
		||||
        self.content_stack.addWidget(self.music_label)
 | 
			
		||||
 | 
			
		||||
        # Чаты
 | 
			
		||||
        self.content_stack.addWidget(QLabel("Контент Чатов"))
 | 
			
		||||
        self.chat_list_view = ChatListView()
 | 
			
		||||
        self.content_stack.addWidget(self.chat_list_view)
 | 
			
		||||
 | 
			
		||||
        # Профиль
 | 
			
		||||
        self.content_stack.addWidget(QLabel("Контент Профиля"))
 | 
			
		||||
 | 
			
		||||
    def update_chat_list(self, chat_data):
 | 
			
		||||
        """
 | 
			
		||||
        Слот для обновления списка чатов.
 | 
			
		||||
        Получает данные из сигнала контроллера.
 | 
			
		||||
        """
 | 
			
		||||
        print(f"[UI] Получены данные для обновления чатов: {len(chat_data.items)} элементов.")
 | 
			
		||||
        # Передаем данные в виджет списка чатов для отображения
 | 
			
		||||
        self.chat_list_view.populate_chats(chat_data.items)
 | 
			
		||||
 | 
			
		||||
    def on_tab_button_clicked(self, index):
 | 
			
		||||
        """Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
 | 
			
		||||
        if index in self.REQUIRED_PERMISSIONS:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user