add chat list
This commit is contained in:
parent
99029541d3
commit
21e8bb0ad0
@ -8,11 +8,15 @@ from threading import Thread
|
|||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
from app.core.database import get_last_login, get_session, set_last_login
|
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):
|
class MainController(QStackedWidget):
|
||||||
# Сигнал для показа уведомлений из любого потока
|
# Сигнал для показа уведомлений из любого потока
|
||||||
# (message, is_error)
|
# (message, is_error)
|
||||||
notification_requested = Signal(str, bool)
|
notification_requested = Signal(str, bool)
|
||||||
|
# Сигнал для передачи загруженных данных чата в основной поток
|
||||||
|
chats_loaded = Signal(object)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -49,8 +53,9 @@ class MainController(QStackedWidget):
|
|||||||
|
|
||||||
self.yobble_home_view = YobbleHomeView(username=username)
|
self.yobble_home_view = YobbleHomeView(username=username)
|
||||||
|
|
||||||
# Подключаем сигнал к слоту в YobbleHomeView
|
# Подключаем сигналы к слотам в YobbleHomeView
|
||||||
self.notification_requested.connect(self.yobble_home_view.show_notification)
|
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.addWidget(self.yobble_home_view)
|
||||||
self.setCurrentWidget(self.yobble_home_view)
|
self.setCurrentWidget(self.yobble_home_view)
|
||||||
@ -72,8 +77,32 @@ class MainController(QStackedWidget):
|
|||||||
# asyncio.run() выполняет async функцию и возвращает её результат
|
# asyncio.run() выполняет async функцию и возвращает её результат
|
||||||
asyncio.run(self.yobble_home_view.preload_permissions())
|
asyncio.run(self.yobble_home_view.preload_permissions())
|
||||||
|
|
||||||
|
# Загружаем список чатов
|
||||||
|
print("[Sync] Загружаем список чатов...")
|
||||||
|
asyncio.run(self.load_chats(username))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = f"Ошибка предзагрузки: {e}"
|
error_message = f"Ошибка предзагрузки: {e}"
|
||||||
print(f"[Sync] {error_message}")
|
print(f"[Sync] {error_message}")
|
||||||
# Отправляем сигнал вместо прямого вызова
|
# Отправляем сигнал вместо прямого вызова
|
||||||
self.notification_requested.emit(error_message, True)
|
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 uuid import UUID
|
||||||
from datetime import datetime
|
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):
|
class PrivateChatListItem(BaseModel):
|
||||||
chat_name: Optional[str]
|
chat_id: UUID = Field(..., description="ID чата")
|
||||||
chat_type: Literal["self", "private"]
|
chat_type: Literal["self", "private"] = Field(..., description="Тип чата")
|
||||||
chat_id: UUID
|
chat_data: Optional[Dict[str, Any]] = Field(default=None, description="Данные о чате")
|
||||||
chat_data: Optional[Dict[str, Any]] = None
|
last_message: Optional[MessageItem] = Field(None, description="Последнее сообщение в чате")
|
||||||
companion_id: Optional[UUID] = None
|
created_at: datetime = Field(..., description="Дата создания чата")
|
||||||
companion_data: Optional[Dict[str, Any]] = None # Типизируй как нужно
|
|
||||||
last_message: Optional[LastMessage] = None
|
|
||||||
created_at: datetime
|
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.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel, QListWidgetItem
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from typing import List
|
||||||
from app.core.models.chat_models import PrivateChatListItem
|
from app.core.models.chat_models import PrivateChatListItem
|
||||||
|
|
||||||
class ChatListView(QWidget):
|
class ChatListView(QWidget):
|
||||||
def __init__(self, username, chat_items: list[PrivateChatListItem]):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(f"Чаты — {username}")
|
|
||||||
self.setMinimumSize(400, 500)
|
|
||||||
|
|
||||||
self.chat_items = chat_items
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
layout = QVBoxLayout()
|
"""Инициализирует пользовательский интерфейс."""
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
self.label = QLabel("Список чатов:")
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
layout.addWidget(self.label)
|
|
||||||
|
|
||||||
self.chat_list = QListWidget()
|
self.chat_list = QListWidget()
|
||||||
self.render_chat_items()
|
self.chat_list.setStyleSheet("QListWidget { border: none; }")
|
||||||
layout.addWidget(self.chat_list)
|
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()
|
self.chat_list.clear()
|
||||||
for chat in self.chat_items:
|
item = QListWidgetItem(text)
|
||||||
companion_name = chat.companion_data.get("username", "Без имени") if chat.companion_data else "Неизвестный"
|
item.setTextAlignment(Qt.AlignCenter)
|
||||||
last_msg = chat.last_message.context if chat.last_message else "Нет сообщений"
|
self.chat_list.addItem(item)
|
||||||
item_text = f"{companion_name} — {last_msg}"
|
|
||||||
self.chat_list.addItem(QListWidgetItem(item_text))
|
|
||||||
|
|
||||||
def update_chat_items(self, new_items: list[PrivateChatListItem]):
|
def populate_chats(self, chat_items: List[PrivateChatListItem]):
|
||||||
self.chat_items = new_items
|
"""
|
||||||
self.render_chat_items()
|
Заполняет список чатов данными, полученными от сервера.
|
||||||
|
"""
|
||||||
|
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.theme import theme_manager
|
||||||
from app.core.dialogs import show_themed_messagebox
|
from app.core.dialogs import show_themed_messagebox
|
||||||
from app.ui.views.side_menu_view import SideMenuView
|
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.services.auth_service import get_user_role
|
||||||
from app.core.database import get_current_access_token
|
from app.core.database import get_current_access_token
|
||||||
from app.core.localizer import localizer
|
from app.core.localizer import localizer
|
||||||
@ -366,11 +367,21 @@ class YobbleHomeView(QWidget):
|
|||||||
self.content_stack.addWidget(self.music_label)
|
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("Контент Профиля"))
|
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):
|
def on_tab_button_clicked(self, index):
|
||||||
"""Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
|
"""Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
|
||||||
if index in self.REQUIRED_PERMISSIONS:
|
if index in self.REQUIRED_PERMISSIONS:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user