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:
|
||||
|
||||
Reference in New Issue
Block a user