Compare commits
No commits in common. "229cab0dde142783fea24525be94b6a88834329c" and "63c07305da866c1980478ca2b074f4ff374fe05a" have entirely different histories.
229cab0dde
...
63c07305da
@ -45,20 +45,13 @@ class MainController(QStackedWidget):
|
|||||||
def handle_login_success(self, username: str):
|
def handle_login_success(self, username: str):
|
||||||
"""Обрабатывает успешный вход в систему."""
|
"""Обрабатывает успешный вход в систему."""
|
||||||
set_last_login(username)
|
set_last_login(username)
|
||||||
session = get_session(username)
|
|
||||||
user_id = session["user_id"] if session else None
|
|
||||||
|
|
||||||
if not user_id:
|
|
||||||
self.show_login()
|
|
||||||
self.notification_requested.emit("Не удалось получить ID пользователя из сессии.", True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.login_view:
|
if self.login_view:
|
||||||
self.login_view.close()
|
self.login_view.close()
|
||||||
self.removeWidget(self.login_view)
|
self.removeWidget(self.login_view)
|
||||||
self.login_view = None
|
self.login_view = None
|
||||||
|
|
||||||
self.yobble_home_view = YobbleHomeView(username=username, current_user_id=user_id)
|
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)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ DEBUG = True
|
|||||||
API_SCHEME = "https"
|
API_SCHEME = "https"
|
||||||
API_HOST = "api.yobble.org"
|
API_HOST = "api.yobble.org"
|
||||||
BASE_URL = f"{API_SCHEME}://{API_HOST}"
|
BASE_URL = f"{API_SCHEME}://{API_HOST}"
|
||||||
APP_VERSION = "0.3_chat"
|
APP_VERSION = "0.2_home_screen"
|
||||||
APP_NAME = "yobble messenger"
|
APP_NAME = "yobble messenger"
|
||||||
APP_HEADER = f"{APP_NAME}"
|
APP_HEADER = f"{APP_NAME}"
|
||||||
if DEBUG: APP_HEADER=f"{APP_HEADER} ({APP_VERSION})"
|
if DEBUG: APP_HEADER=f"{APP_HEADER} ({APP_VERSION})"
|
||||||
|
|||||||
@ -28,7 +28,6 @@ def init_db():
|
|||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
login TEXT PRIMARY KEY,
|
login TEXT PRIMARY KEY,
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
access_token TEXT NOT NULL,
|
access_token TEXT NOT NULL,
|
||||||
refresh_token TEXT NOT NULL,
|
refresh_token TEXT NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL
|
created_at TIMESTAMP NOT NULL
|
||||||
@ -51,12 +50,10 @@ def init_db():
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def add_session(login, access_token, refresh_token, user_id=None, update_existing=False):
|
def add_session(login, access_token, refresh_token, update_existing=False):
|
||||||
"""Добавляет новую сессию или обновляет существующую."""
|
"""Добавляет новую сессию или обновляет существующую."""
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
print("ffff", login, access_token, refresh_token, user_id, update_existing)
|
|
||||||
|
|
||||||
if update_existing:
|
if update_existing:
|
||||||
# Обновляем существующую сессию по access_token
|
# Обновляем существующую сессию по access_token
|
||||||
@ -68,9 +65,9 @@ def add_session(login, access_token, refresh_token, user_id=None, update_existin
|
|||||||
else:
|
else:
|
||||||
# Вставляем новую или заменяем существующую по логину
|
# Вставляем новую или заменяем существующую по логину
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT OR REPLACE INTO sessions (login, user_id, access_token, refresh_token, created_at)
|
INSERT OR REPLACE INTO sessions (login, access_token, refresh_token, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (login, user_id, access_token, refresh_token, datetime.now()))
|
''', (login, access_token, refresh_token, datetime.now()))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@ -44,35 +44,4 @@ class PrivateChatListData(BaseModel):
|
|||||||
|
|
||||||
class PrivateChatListResponse(BaseModel):
|
class PrivateChatListResponse(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
data: PrivateChatListData
|
data: PrivateChatListData
|
||||||
|
|
||||||
|
|
||||||
# history
|
|
||||||
class PrivateChatHistoryData(BaseModel):
|
|
||||||
items: List[MessageItem]
|
|
||||||
has_more: bool
|
|
||||||
|
|
||||||
|
|
||||||
class PrivateChatHistoryResponse(BaseModel):
|
|
||||||
status: str
|
|
||||||
data: PrivateChatHistoryData
|
|
||||||
|
|
||||||
|
|
||||||
# send
|
|
||||||
class PrivateMessageSendRequest(BaseModel):
|
|
||||||
chat_id: UUID
|
|
||||||
content: Optional[str] = Field(None, description="Содержимое сообщения (макс. 4096 символов)", max_length=4096)
|
|
||||||
message_type: List[Literal["text"]] = Field(
|
|
||||||
..., description="Один или несколько типов сообщения"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class PrivateMessageSendData(BaseModel):
|
|
||||||
message_id: int
|
|
||||||
chat_id: UUID
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class PrivateMessageSendResponse(BaseModel):
|
|
||||||
status: str
|
|
||||||
data: PrivateMessageSendData
|
|
||||||
@ -25,8 +25,7 @@ async def login(login, password):
|
|||||||
add_session(
|
add_session(
|
||||||
login=login,
|
login=login,
|
||||||
access_token=token_data["access_token"],
|
access_token=token_data["access_token"],
|
||||||
refresh_token=token_data["refresh_token"],
|
refresh_token=token_data["refresh_token"]
|
||||||
user_id=token_data["user_id"]
|
|
||||||
)
|
)
|
||||||
return True, localizer.translate("Успешный вход")
|
return True, localizer.translate("Успешный вход")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
import httpx
|
import httpx
|
||||||
from app.core import config
|
from app.core import config
|
||||||
from app.core.localizer import localizer
|
from app.core.localizer import localizer
|
||||||
from app.core.models.chat_models import (
|
from app.core.models.chat_models import PrivateChatListResponse, PrivateChatListData
|
||||||
PrivateChatListResponse, PrivateChatListData,
|
|
||||||
PrivateChatHistoryResponse, PrivateChatHistoryData,
|
|
||||||
PrivateMessageSendRequest, PrivateMessageSendResponse
|
|
||||||
)
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
|
async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
|
||||||
"""
|
"""
|
||||||
@ -51,91 +46,3 @@ async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
|
|||||||
return False, f"{localizer.translate('Ошибка сети')}: {e}"
|
return False, f"{localizer.translate('Ошибка сети')}: {e}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"{localizer.translate('Произошла ошибка')}: {e}"
|
return False, f"{localizer.translate('Произошла ошибка')}: {e}"
|
||||||
|
|
||||||
async def get_chat_history(token: str, chat_id: UUID, before_message_id: int = None, limit: int = 30):
|
|
||||||
"""
|
|
||||||
Получает историю сообщений для указанного приватного чата.
|
|
||||||
|
|
||||||
:param token: Токен доступа
|
|
||||||
:param chat_id: ID чата
|
|
||||||
:param before_message_id: ID сообщения для пагинации (загрузка более старых)
|
|
||||||
:param limit: Количество сообщений для загрузки
|
|
||||||
:return: Кортеж (успех: bool, данные: PrivateChatHistoryData | str)
|
|
||||||
"""
|
|
||||||
url = f"{config.BASE_URL}/v1/chat/private/history"
|
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
params = {"chat_id": str(chat_id), "limit": limit}
|
|
||||||
if before_message_id:
|
|
||||||
params["before_message_id"] = before_message_id
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient(http2=True) as client:
|
|
||||||
response = await client.get(url, headers=headers, params=params)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
if data.get("status") == "fine":
|
|
||||||
response_model = PrivateChatHistoryResponse(**data)
|
|
||||||
return True, response_model.data
|
|
||||||
else:
|
|
||||||
return False, data.get("detail", "Неизвестная ошибка ответа")
|
|
||||||
|
|
||||||
elif response.status_code in [401, 403]:
|
|
||||||
error_data = response.json()
|
|
||||||
return False, error_data.get("detail", "Ошибка аутентификации или доступа")
|
|
||||||
|
|
||||||
elif response.status_code == 422:
|
|
||||||
return False, "Некорректные параметры запроса"
|
|
||||||
|
|
||||||
else:
|
|
||||||
return False, f"Ошибка сервера: {response.status_code}"
|
|
||||||
|
|
||||||
except httpx.RequestError as e:
|
|
||||||
return False, f"Ошибка сети: {e}"
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"Произошла ошибка: {e}"
|
|
||||||
|
|
||||||
|
|
||||||
async def send_private_message(token: str, payload: "PrivateMessageSendRequest"):
|
|
||||||
"""
|
|
||||||
Отправляет приватное сообщение в чат.
|
|
||||||
|
|
||||||
:param token: Токен доступа
|
|
||||||
:param payload: Данные сообщения (Pydantic модель PrivateMessageSendRequest)
|
|
||||||
:return: Кортеж (успех: bool, данные: PrivateMessageSendData | str)
|
|
||||||
"""
|
|
||||||
url = f"{config.BASE_URL}/v1/chat/private/send"
|
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient(http2=True) as client:
|
|
||||||
response = await client.post(url, headers=headers, json=payload.model_dump(mode='json'))
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
if data.get("status") == "fine":
|
|
||||||
response_model = PrivateMessageSendResponse(**data)
|
|
||||||
return True, response_model.data
|
|
||||||
else:
|
|
||||||
return False, data.get("detail", "Неизвестная ошибка ответа")
|
|
||||||
|
|
||||||
elif response.status_code in [401, 403, 404]:
|
|
||||||
error_data = response.json()
|
|
||||||
print("error_data", error_data)
|
|
||||||
return False, error_data.get("detail", "Ошибка доступа или чат не найден")
|
|
||||||
|
|
||||||
elif response.status_code == 422:
|
|
||||||
error_data = response.json()
|
|
||||||
# Может быть список ошибок
|
|
||||||
detail = error_data.get("detail")
|
|
||||||
if isinstance(detail, list):
|
|
||||||
return False, ", ".join([e.get("msg", "Неизвестная ошибка валидации") for e in detail])
|
|
||||||
return False, detail or "Некорректные данные"
|
|
||||||
|
|
||||||
else:
|
|
||||||
return False, f"Ошибка сервера: {response.status_code}"
|
|
||||||
|
|
||||||
except httpx.RequestError as e:
|
|
||||||
return False, f"Ошибка сети: {e}"
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"Произошла ошибка: {e}"
|
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QListWidgetItem
|
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QListWidgetItem
|
||||||
from PySide6.QtCore import Qt, QSize, Signal
|
from PySide6.QtCore import Qt, QSize
|
||||||
from typing import List
|
from typing import List
|
||||||
from app.core.models.chat_models import PrivateChatListItem
|
from app.core.models.chat_models import PrivateChatListItem
|
||||||
from app.ui.widgets.chat_list_item_widget import ChatListItemWidget
|
from app.ui.widgets.chat_list_item_widget import ChatListItemWidget
|
||||||
from app.core.theme import theme_manager
|
from app.core.theme import theme_manager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
class ChatListView(QWidget):
|
class ChatListView(QWidget):
|
||||||
chat_selected = Signal(UUID)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.chat_items_map = {}
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.update_theme()
|
self.update_theme()
|
||||||
theme_manager.theme_changed.connect(self.update_theme)
|
theme_manager.theme_changed.connect(self.update_theme)
|
||||||
@ -25,18 +21,11 @@ class ChatListView(QWidget):
|
|||||||
|
|
||||||
self.chat_list = QListWidget()
|
self.chat_list = QListWidget()
|
||||||
self.chat_list.setSpacing(2)
|
self.chat_list.setSpacing(2)
|
||||||
self.chat_list.itemClicked.connect(self.on_chat_item_clicked)
|
|
||||||
layout.addWidget(self.chat_list)
|
layout.addWidget(self.chat_list)
|
||||||
|
|
||||||
# Изначальное состояние
|
# Изначальное состояние
|
||||||
self.show_placeholder_message("Загрузка чатов...")
|
self.show_placeholder_message("Загрузка чатов...")
|
||||||
|
|
||||||
def on_chat_item_clicked(self, item: QListWidgetItem):
|
|
||||||
"""Обработчик клика по элементу списка чатов."""
|
|
||||||
chat_id = self.chat_items_map.get(id(item))
|
|
||||||
if chat_id:
|
|
||||||
self.chat_selected.emit(chat_id)
|
|
||||||
|
|
||||||
def update_theme(self):
|
def update_theme(self):
|
||||||
"""Обновляет стили в соответствии с темой."""
|
"""Обновляет стили в соответствии с темой."""
|
||||||
palette = theme_manager.get_current_palette()
|
palette = theme_manager.get_current_palette()
|
||||||
@ -92,7 +81,6 @@ class ChatListView(QWidget):
|
|||||||
Заполняет список чатов данными, полученными от сервера.
|
Заполняет список чатов данными, полученными от сервера.
|
||||||
"""
|
"""
|
||||||
self.chat_list.clear()
|
self.chat_list.clear()
|
||||||
self.chat_items_map.clear()
|
|
||||||
|
|
||||||
if not chat_items:
|
if not chat_items:
|
||||||
self.show_placeholder_message("У вас пока нет чатов")
|
self.show_placeholder_message("У вас пока нет чатов")
|
||||||
@ -129,6 +117,3 @@ class ChatListView(QWidget):
|
|||||||
list_item.setSizeHint(item_widget.sizeHint())
|
list_item.setSizeHint(item_widget.sizeHint())
|
||||||
self.chat_list.addItem(list_item)
|
self.chat_list.addItem(list_item)
|
||||||
self.chat_list.setItemWidget(list_item, item_widget)
|
self.chat_list.setItemWidget(list_item, item_widget)
|
||||||
|
|
||||||
# Сохраняем ID чата в словаре
|
|
||||||
self.chat_items_map[id(list_item)] = chat.chat_id
|
|
||||||
|
|||||||
@ -1,124 +0,0 @@
|
|||||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QListWidget, QLineEdit, QPushButton, QHBoxLayout, QListWidgetItem
|
|
||||||
from PySide6.QtCore import Qt, Signal
|
|
||||||
from app.core.models.chat_models import MessageItem
|
|
||||||
from app.ui.widgets.message_bubble_widget import MessageBubbleWidget
|
|
||||||
from app.core.theme import theme_manager
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
class ChatView(QWidget):
|
|
||||||
# Сигнал, который отправляет текст сообщения для отправки
|
|
||||||
send_message_requested = Signal(str)
|
|
||||||
|
|
||||||
def __init__(self, chat_id: UUID, current_user_id: UUID):
|
|
||||||
super().__init__()
|
|
||||||
self.chat_id = chat_id
|
|
||||||
self.current_user_id = current_user_id
|
|
||||||
self.init_ui()
|
|
||||||
self.update_theme()
|
|
||||||
theme_manager.theme_changed.connect(self.update_theme)
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
"""Инициализирует пользовательский интерфейс."""
|
|
||||||
main_layout = QVBoxLayout(self)
|
|
||||||
main_layout.setSpacing(0)
|
|
||||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
|
|
||||||
self.message_list = QListWidget()
|
|
||||||
self.message_list.setSpacing(10)
|
|
||||||
self.message_list.setWordWrap(True)
|
|
||||||
|
|
||||||
input_layout = QHBoxLayout()
|
|
||||||
input_layout.setSpacing(10)
|
|
||||||
input_layout.setContentsMargins(10, 10, 10, 10)
|
|
||||||
|
|
||||||
self.message_input = QLineEdit()
|
|
||||||
self.message_input.setPlaceholderText("Введите сообщение...")
|
|
||||||
|
|
||||||
self.send_button = QPushButton("Отправить")
|
|
||||||
|
|
||||||
input_layout.addWidget(self.message_input)
|
|
||||||
input_layout.addWidget(self.send_button)
|
|
||||||
|
|
||||||
main_layout.addWidget(self.message_list)
|
|
||||||
main_layout.addLayout(input_layout)
|
|
||||||
|
|
||||||
# --- Подключение сигналов ---
|
|
||||||
self.send_button.clicked.connect(self._on_send)
|
|
||||||
self.message_input.returnPressed.connect(self._on_send)
|
|
||||||
|
|
||||||
def _on_send(self):
|
|
||||||
"""Обработчик нажатия кнопки отправки."""
|
|
||||||
message_text = self.message_input.text().strip()
|
|
||||||
if message_text:
|
|
||||||
self.send_message_requested.emit(message_text)
|
|
||||||
|
|
||||||
def clear_input(self):
|
|
||||||
"""Очищает поле ввода."""
|
|
||||||
self.message_input.clear()
|
|
||||||
|
|
||||||
def update_theme(self):
|
|
||||||
"""Обновляет стили в соответствии с темой."""
|
|
||||||
palette = theme_manager.get_current_palette()
|
|
||||||
self.setStyleSheet(f"background-color: {palette['primary']};")
|
|
||||||
self.message_list.setStyleSheet(f"""
|
|
||||||
QListWidget {{
|
|
||||||
background-color: {palette['primary']};
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
self.message_input.setStyleSheet(f"""
|
|
||||||
QLineEdit {{
|
|
||||||
background-color: {palette['secondary']};
|
|
||||||
color: {palette['text']};
|
|
||||||
border: 1px solid {palette['border']};
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 5px 15px;
|
|
||||||
font-size: 10pt;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
self.send_button.setStyleSheet(f"""
|
|
||||||
QPushButton {{
|
|
||||||
background-color: {palette['accent']};
|
|
||||||
color: #ffffff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 5px 15px;
|
|
||||||
font-size: 10pt;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
QPushButton:hover {{
|
|
||||||
background-color: #4a8ac0;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
|
|
||||||
def add_message(self, message: MessageItem):
|
|
||||||
"""Добавляет сообщение в список."""
|
|
||||||
is_own = message.sender_id == self.current_user_id
|
|
||||||
|
|
||||||
bubble = MessageBubbleWidget(
|
|
||||||
text=message.content,
|
|
||||||
timestamp=message.created_at.strftime('%H:%M'),
|
|
||||||
is_own=is_own
|
|
||||||
)
|
|
||||||
|
|
||||||
item = QListWidgetItem(self.message_list)
|
|
||||||
item.setSizeHint(bubble.sizeHint())
|
|
||||||
|
|
||||||
# Выравнивание сообщения
|
|
||||||
if is_own:
|
|
||||||
item.setTextAlignment(Qt.AlignRight)
|
|
||||||
else:
|
|
||||||
item.setTextAlignment(Qt.AlignLeft)
|
|
||||||
|
|
||||||
self.message_list.addItem(item)
|
|
||||||
self.message_list.setItemWidget(item, bubble)
|
|
||||||
self.message_list.scrollToBottom()
|
|
||||||
|
|
||||||
def populate_history(self, messages: list[MessageItem]):
|
|
||||||
"""Заполняет список сообщениями из истории."""
|
|
||||||
self.message_list.clear()
|
|
||||||
# Сортируем сообщения по дате (от старых к новым)
|
|
||||||
messages.sort(key=lambda m: m.created_at)
|
|
||||||
for message in messages:
|
|
||||||
self.add_message(message)
|
|
||||||
@ -15,10 +15,6 @@ 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
|
||||||
from app.ui.views.chat_view import ChatView
|
|
||||||
from app.core.services.chat_service import get_chat_history, send_private_message
|
|
||||||
from app.core.models.chat_models import PrivateMessageSendRequest, MessageItem
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
class YobbleHomeView(QWidget):
|
class YobbleHomeView(QWidget):
|
||||||
REQUIRED_PERMISSIONS = {
|
REQUIRED_PERMISSIONS = {
|
||||||
@ -26,17 +22,15 @@ class YobbleHomeView(QWidget):
|
|||||||
1: "music.access" # Музыка
|
1: "music.access" # Музыка
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, username: str, current_user_id: UUID):
|
def __init__(self, username: str):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.username = username
|
self.username = username
|
||||||
self.current_user_id = current_user_id
|
|
||||||
self.setWindowTitle(f"Yobble Home - {username}")
|
self.setWindowTitle(f"Yobble Home - {username}")
|
||||||
self.setMinimumSize(360, 640)
|
self.setMinimumSize(360, 640)
|
||||||
self.permission_cache = set()
|
self.permission_cache = set()
|
||||||
self.permissions_preloaded = False
|
self.permissions_preloaded = False
|
||||||
self.permissions_preloaded = False
|
self.permissions_preloaded = False
|
||||||
self.permissions_preloaded_last = 0.0
|
self.permissions_preloaded_last = 0.0
|
||||||
self.chat_view = None # Placeholder for the chat view
|
|
||||||
|
|
||||||
# --- Основной макет ---
|
# --- Основной макет ---
|
||||||
# Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом
|
# Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом
|
||||||
@ -243,14 +237,6 @@ class YobbleHomeView(QWidget):
|
|||||||
top_bar_layout = QHBoxLayout(top_bar_widget)
|
top_bar_layout = QHBoxLayout(top_bar_widget)
|
||||||
top_bar_layout.setContentsMargins(10, 5, 10, 5)
|
top_bar_layout.setContentsMargins(10, 5, 10, 5)
|
||||||
|
|
||||||
self.back_button = QPushButton("‹")
|
|
||||||
self.back_button.setObjectName("BackButton")
|
|
||||||
self.back_button.setFocusPolicy(Qt.NoFocus)
|
|
||||||
self.back_button.setCursor(Qt.PointingHandCursor)
|
|
||||||
self.back_button.clicked.connect(self.close_chat_view)
|
|
||||||
self.back_button.hide()
|
|
||||||
top_bar_layout.addWidget(self.back_button)
|
|
||||||
|
|
||||||
self.burger_menu_button = QPushButton("☰")
|
self.burger_menu_button = QPushButton("☰")
|
||||||
self.burger_menu_button.setObjectName("BurgerMenuButton")
|
self.burger_menu_button.setObjectName("BurgerMenuButton")
|
||||||
self.burger_menu_button.setFocusPolicy(Qt.NoFocus)
|
self.burger_menu_button.setFocusPolicy(Qt.NoFocus)
|
||||||
@ -279,6 +265,16 @@ class YobbleHomeView(QWidget):
|
|||||||
top_bar_layout.addWidget(self.notification_button)
|
top_bar_layout.addWidget(self.notification_button)
|
||||||
self.notification_button.clicked.connect(self.handle_notification_click)
|
self.notification_button.clicked.connect(self.handle_notification_click)
|
||||||
|
|
||||||
|
# --- Временные кнопки для теста ---
|
||||||
|
# self.test_ok_button = QPushButton("Test OK")
|
||||||
|
# self.test_ok_button.clicked.connect(lambda: self.show_notification("Операция прошла успешно", is_error=False))
|
||||||
|
# top_bar_layout.addWidget(self.test_ok_button)
|
||||||
|
|
||||||
|
# self.test_err_button = QPushButton("Test Error")
|
||||||
|
# self.test_err_button.clicked.connect(lambda: self.show_notification("Произошла ошибка при обновлении", is_error=True))
|
||||||
|
# top_bar_layout.addWidget(self.test_err_button)
|
||||||
|
# --- Конец временного кода ---
|
||||||
|
|
||||||
return top_bar_widget
|
return top_bar_widget
|
||||||
|
|
||||||
def create_bottom_bar(self):
|
def create_bottom_bar(self):
|
||||||
@ -372,16 +368,11 @@ class YobbleHomeView(QWidget):
|
|||||||
|
|
||||||
# Чаты
|
# Чаты
|
||||||
self.chat_list_view = ChatListView()
|
self.chat_list_view = ChatListView()
|
||||||
self.chat_list_view.chat_selected.connect(self.open_chat_view)
|
|
||||||
self.content_stack.addWidget(self.chat_list_view)
|
self.content_stack.addWidget(self.chat_list_view)
|
||||||
|
|
||||||
# Профиль
|
# Профиль
|
||||||
self.content_stack.addWidget(QLabel("Контент Профиля"))
|
self.content_stack.addWidget(QLabel("Контент Профиля"))
|
||||||
|
|
||||||
# Placeholder для открытого чата
|
|
||||||
self.chat_view_container = QWidget()
|
|
||||||
self.content_stack.addWidget(self.chat_view_container)
|
|
||||||
|
|
||||||
def update_chat_list(self, chat_data):
|
def update_chat_list(self, chat_data):
|
||||||
"""
|
"""
|
||||||
Слот для обновления списка чатов.
|
Слот для обновления списка чатов.
|
||||||
@ -438,6 +429,19 @@ class YobbleHomeView(QWidget):
|
|||||||
raise ConnectionError(data)
|
raise ConnectionError(data)
|
||||||
return success, data
|
return success, data
|
||||||
|
|
||||||
|
# def update_current_tab_content(self):
|
||||||
|
# """Обновляет контент текущей активной вкладки после предзагрузки прав."""
|
||||||
|
# current_index = self.content_stack.currentIndex()
|
||||||
|
|
||||||
|
# # Проверяем, требует ли текущая вкладка прав доступа
|
||||||
|
# if current_index in self.REQUIRED_PERMISSIONS:
|
||||||
|
# permission_code = self.REQUIRED_PERMISSIONS[current_index]
|
||||||
|
|
||||||
|
# if permission_code in self.permission_cache:
|
||||||
|
# self.show_real_content(current_index)
|
||||||
|
# else:
|
||||||
|
# self.show_denied(current_index)
|
||||||
|
|
||||||
async def check_permissions_and_switch(self, index, permission_code):
|
async def check_permissions_and_switch(self, index, permission_code):
|
||||||
"""Асинхронно проверяет права и переключает вкладку."""
|
"""Асинхронно проверяет права и переключает вкладку."""
|
||||||
self.preload_permissions_first = False
|
self.preload_permissions_first = False
|
||||||
@ -476,101 +480,6 @@ class YobbleHomeView(QWidget):
|
|||||||
titles = ["Лента", "Музыка", "Чаты", "Лицо"]
|
titles = ["Лента", "Музыка", "Чаты", "Лицо"]
|
||||||
self.title_label.setText(titles[index])
|
self.title_label.setText(titles[index])
|
||||||
|
|
||||||
def open_chat_view(self, chat_id: UUID):
|
|
||||||
"""Открывает виджет чата, загружая его историю."""
|
|
||||||
print(f"Opening chat: {chat_id}")
|
|
||||||
asyncio.create_task(self.load_and_display_chat(chat_id))
|
|
||||||
|
|
||||||
async def load_and_display_chat(self, chat_id: UUID):
|
|
||||||
"""Асинхронно загружает историю и отображает чат."""
|
|
||||||
token = await get_current_access_token()
|
|
||||||
if not token:
|
|
||||||
self.show_notification("Ошибка: сессия не найдена.", is_error=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.show_notification("Загрузка истории...", is_error=False, duration=1000)
|
|
||||||
success, data = await get_chat_history(token, chat_id)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
if self.chat_view:
|
|
||||||
self.content_stack.removeWidget(self.chat_view)
|
|
||||||
self.chat_view.deleteLater()
|
|
||||||
|
|
||||||
self.chat_view = ChatView(chat_id, self.current_user_id)
|
|
||||||
self.chat_view.populate_history(data.items)
|
|
||||||
# Подключаем сигнал отправки сообщения к нашему новому методу
|
|
||||||
self.chat_view.send_message_requested.connect(self.send_message)
|
|
||||||
|
|
||||||
# Заменяем плейсхолдер на реальный виджет
|
|
||||||
self.content_stack.removeWidget(self.chat_view_container)
|
|
||||||
self.content_stack.addWidget(self.chat_view)
|
|
||||||
self.content_stack.setCurrentWidget(self.chat_view)
|
|
||||||
|
|
||||||
self.bottom_bar.hide()
|
|
||||||
self.burger_menu_button.hide()
|
|
||||||
self.back_button.show()
|
|
||||||
else:
|
|
||||||
self.show_notification(f"Не удалось загрузить историю: {data}", is_error=True)
|
|
||||||
|
|
||||||
def send_message(self, content: str):
|
|
||||||
"""Слот для отправки сообщения. Запускает асинхронную задачу."""
|
|
||||||
if not self.chat_view:
|
|
||||||
return
|
|
||||||
|
|
||||||
chat_id = self.chat_view.chat_id
|
|
||||||
payload = PrivateMessageSendRequest(
|
|
||||||
chat_id=chat_id,
|
|
||||||
content=content,
|
|
||||||
message_type=["text"]
|
|
||||||
)
|
|
||||||
asyncio.create_task(self.do_send_message(payload))
|
|
||||||
|
|
||||||
async def do_send_message(self, payload: PrivateMessageSendRequest):
|
|
||||||
"""Асинхронно отправляет сообщение и обновляет UI."""
|
|
||||||
token = await get_current_access_token()
|
|
||||||
if not token:
|
|
||||||
self.show_notification("Ошибка: сессия не найдена.", is_error=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
success, data = await send_private_message(token, payload)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
# В случае успеха, создаем объект сообщения и добавляем в чат
|
|
||||||
# Используем текущее время, т.к. сервер возвращает время с UTC
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
new_message = MessageItem(
|
|
||||||
message_id=data.message_id,
|
|
||||||
chat_id=data.chat_id,
|
|
||||||
sender_id=self.current_user_id,
|
|
||||||
content=payload.content,
|
|
||||||
message_type=['text'],
|
|
||||||
is_viewed=True, # Свои сообщения считаем просмотренными
|
|
||||||
created_at=datetime.now(),
|
|
||||||
forward_metadata=None,
|
|
||||||
sender_data=None,
|
|
||||||
media_link=None,
|
|
||||||
updated_at=None
|
|
||||||
)
|
|
||||||
self.chat_view.add_message(new_message)
|
|
||||||
self.chat_view.clear_input() # Очищаем поле ввода
|
|
||||||
else:
|
|
||||||
self.show_notification(f"Ошибка отправки: {data}", is_error=True)
|
|
||||||
|
|
||||||
def close_chat_view(self):
|
|
||||||
"""Закрывает виджет чата и возвращается к списку."""
|
|
||||||
self.content_stack.setCurrentWidget(self.chat_list_view)
|
|
||||||
self.bottom_bar.show()
|
|
||||||
self.burger_menu_button.show()
|
|
||||||
self.back_button.hide()
|
|
||||||
if self.chat_view:
|
|
||||||
self.content_stack.removeWidget(self.chat_view)
|
|
||||||
self.chat_view.deleteLater()
|
|
||||||
self.chat_view = None
|
|
||||||
# Возвращаем плейсхолдер
|
|
||||||
self.chat_view_container = QWidget()
|
|
||||||
self.content_stack.addWidget(self.chat_view_container)
|
|
||||||
|
|
||||||
def show_error_message(self, message):
|
def show_error_message(self, message):
|
||||||
"""Показывает всплывающее уведомление об ошибке."""
|
"""Показывает всплывающее уведомление об ошибке."""
|
||||||
self.show_notification(message, is_error=True)
|
self.show_notification(message, is_error=True)
|
||||||
@ -706,10 +615,6 @@ class YobbleHomeView(QWidget):
|
|||||||
color: {title_color};
|
color: {title_color};
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}}
|
}}
|
||||||
#BackButton {{
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
#TitleLabel {{
|
#TitleLabel {{
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from app.core.theme import theme_manager
|
|
||||||
|
|
||||||
class MessageBubbleWidget(QWidget):
|
|
||||||
def __init__(self, text: str, timestamp: str, is_own: bool):
|
|
||||||
super().__init__()
|
|
||||||
self.init_ui(text, timestamp, is_own)
|
|
||||||
self.update_theme()
|
|
||||||
|
|
||||||
def init_ui(self, text: str, timestamp: str, is_own: bool):
|
|
||||||
"""Инициализирует пользовательский интерфейс."""
|
|
||||||
self.layout = QVBoxLayout(self)
|
|
||||||
self.layout.setSpacing(2)
|
|
||||||
|
|
||||||
self.text_label = QLabel(text)
|
|
||||||
self.text_label.setWordWrap(True)
|
|
||||||
|
|
||||||
self.timestamp_label = QLabel(timestamp)
|
|
||||||
self.timestamp_label.setAlignment(Qt.AlignRight)
|
|
||||||
|
|
||||||
self.layout.addWidget(self.text_label)
|
|
||||||
self.layout.addWidget(self.timestamp_label)
|
|
||||||
|
|
||||||
self.is_own = is_own
|
|
||||||
self.setObjectName("MessageBubble")
|
|
||||||
self.update_theme()
|
|
||||||
|
|
||||||
def update_theme(self):
|
|
||||||
"""Обновляет стили виджета в соответствии с текущей темой."""
|
|
||||||
palette = theme_manager.get_current_palette()
|
|
||||||
|
|
||||||
if self.is_own:
|
|
||||||
bg_color = palette['accent']
|
|
||||||
text_color = "#ffffff"
|
|
||||||
timestamp_color = "#dddddd"
|
|
||||||
alignment = Qt.AlignRight
|
|
||||||
else:
|
|
||||||
bg_color = palette['secondary']
|
|
||||||
text_color = palette['text']
|
|
||||||
timestamp_color = palette['text_secondary']
|
|
||||||
alignment = Qt.AlignLeft
|
|
||||||
|
|
||||||
self.setStyleSheet(f"""
|
|
||||||
#MessageBubble {{
|
|
||||||
background-color: {bg_color};
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
self.text_label.setStyleSheet(f"color: {text_color}; font-size: 10pt;")
|
|
||||||
self.timestamp_label.setStyleSheet(f"color: {timestamp_color}; font-size: 8pt;")
|
|
||||||
|
|
||||||
# Устанавливаем выравнивание для всего layout
|
|
||||||
parent_layout = self.parentWidget().layout() if self.parentWidget() else None
|
|
||||||
if parent_layout:
|
|
||||||
# Этот способ не сработает напрямую, выравнивание нужно делать в QListWidgetItem
|
|
||||||
pass
|
|
||||||
Loading…
x
Reference in New Issue
Block a user