mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 19:26:10 +00:00
UNSTABLE | Backup for tomorrow. Will continue working tomorrow most likely. 00:38 - Session ended.
This commit is contained in:
parent
c01d5954dc
commit
c1355b7bf9
156
src/widgets.py
156
src/widgets.py
@ -5,6 +5,7 @@ from textual.widget import Widget
|
|||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
||||||
from textual.app import ComposeResult, RenderResult
|
from textual.app import ComposeResult, RenderResult
|
||||||
|
from textual.keys import Keys
|
||||||
from telethon import TelegramClient, events, utils
|
from telethon import TelegramClient, events, utils
|
||||||
import datetime
|
import datetime
|
||||||
import unicodedata
|
import unicodedata
|
||||||
@ -73,14 +74,47 @@ def convert_image_to_ascii(image_path: str, width: int = 50) -> str:
|
|||||||
log(f"Ошибка конвертации изображения: {e}")
|
log(f"Ошибка конвертации изображения: {e}")
|
||||||
return "Ошибка загрузки изображения"
|
return "Ошибка загрузки изображения"
|
||||||
|
|
||||||
class Chat(Widget):
|
class Chat(Static):
|
||||||
"""Класс виджета чата для панели чатов"""
|
"""Класс виджета чата для панели чатов"""
|
||||||
|
|
||||||
username: Reactive[str] = Reactive(" ", recompose=True)
|
DEFAULT_CSS = """
|
||||||
msg: Reactive[str] = Reactive(" ", recompose=True)
|
Chat {
|
||||||
peer_id: Reactive[int] = Reactive(0)
|
width: 100%;
|
||||||
is_selected: Reactive[bool] = Reactive(False)
|
height: auto;
|
||||||
is_focused: Reactive[bool] = Reactive(False)
|
min-height: 3;
|
||||||
|
padding: 1 2;
|
||||||
|
border: solid $accent;
|
||||||
|
margin: 1 0;
|
||||||
|
background: $surface;
|
||||||
|
}
|
||||||
|
Chat:hover {
|
||||||
|
background: $accent 20%;
|
||||||
|
}
|
||||||
|
Chat.-selected {
|
||||||
|
background: $accent 30%;
|
||||||
|
}
|
||||||
|
Chat:focus {
|
||||||
|
background: $accent 40%;
|
||||||
|
border: double $accent;
|
||||||
|
}
|
||||||
|
.chat-avatar {
|
||||||
|
width: 3;
|
||||||
|
height: 3;
|
||||||
|
content-align: center middle;
|
||||||
|
border: solid $accent;
|
||||||
|
}
|
||||||
|
.chat-content {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 1;
|
||||||
|
}
|
||||||
|
.chat-name {
|
||||||
|
color: $text;
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
.chat-message {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -89,25 +123,64 @@ class Chat(Widget):
|
|||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
disabled: bool = False
|
disabled: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||||
name=str(name),
|
self.can_focus = True
|
||||||
id=id,
|
self._username = ""
|
||||||
classes=classes,
|
self._msg = ""
|
||||||
disabled=disabled
|
self._peer_id = 0
|
||||||
)
|
self._is_selected = False
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.switcher = self.screen.query_one(Horizontal).query_one("#dialog_switcher", ContentSwitcher)
|
self.switcher = self.screen.query_one("#dialog_switcher", ContentSwitcher)
|
||||||
|
|
||||||
def on_click(self) -> None:
|
@property
|
||||||
|
def username(self) -> str:
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@username.setter
|
||||||
|
def username(self, value: str) -> None:
|
||||||
|
self._username = value
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def msg(self) -> str:
|
||||||
|
return self._msg
|
||||||
|
|
||||||
|
@msg.setter
|
||||||
|
def msg(self, value: str) -> None:
|
||||||
|
self._msg = value
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def peer_id(self) -> int:
|
||||||
|
return self._peer_id
|
||||||
|
|
||||||
|
@peer_id.setter
|
||||||
|
def peer_id(self, value: int) -> None:
|
||||||
|
self._peer_id = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_selected(self) -> bool:
|
||||||
|
return self._is_selected
|
||||||
|
|
||||||
|
@is_selected.setter
|
||||||
|
def is_selected(self, value: bool) -> None:
|
||||||
|
self._is_selected = value
|
||||||
|
self.set_class(value, "-selected")
|
||||||
|
|
||||||
|
def on_focus(self) -> None:
|
||||||
# Снимаем выделение со всех чатов
|
# Снимаем выделение со всех чатов
|
||||||
for chat in self.screen.query(Chat):
|
for chat in self.screen.query(Chat):
|
||||||
chat.is_selected = False
|
chat.is_selected = False
|
||||||
chat.is_focused = False
|
|
||||||
|
|
||||||
# Выделяем текущий чат
|
# Выделяем текущий чат
|
||||||
self.is_selected = True
|
self.is_selected = True
|
||||||
self.is_focused = True
|
|
||||||
|
# Прокручиваем к этому чату
|
||||||
|
self.screen.chat_container.scroll_to(self, animate=False)
|
||||||
|
|
||||||
|
def on_click(self) -> None:
|
||||||
|
self.focus()
|
||||||
|
|
||||||
dialog_id = f"dialog-{str(self.peer_id)}"
|
dialog_id = f"dialog-{str(self.peer_id)}"
|
||||||
try:
|
try:
|
||||||
@ -116,24 +189,41 @@ class Chat(Widget):
|
|||||||
chat_id=self.peer_id,
|
chat_id=self.peer_id,
|
||||||
id=dialog_id
|
id=dialog_id
|
||||||
))
|
))
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
log(f"Ошибка открытия диалога: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
self.switcher.current = dialog_id
|
self.switcher.current = dialog_id
|
||||||
self.switcher.recompose()
|
|
||||||
|
def on_key(self, event: Keys) -> None:
|
||||||
|
if event.key == "enter":
|
||||||
|
self.on_click()
|
||||||
|
elif event.key == "up" and self.id != "chat-1":
|
||||||
|
# Фокусируемся на предыдущем чате
|
||||||
|
prev_chat = self.screen.chat_container.query_one(f"#chat-{int(self.id.split('-')[1]) - 1}")
|
||||||
|
if prev_chat:
|
||||||
|
prev_chat.focus()
|
||||||
|
elif event.key == "down":
|
||||||
|
# Фокусируемся на следующем чате
|
||||||
|
next_chat = self.screen.chat_container.query_one(f"#chat-{int(self.id.split('-')[1]) + 1}")
|
||||||
|
if next_chat:
|
||||||
|
next_chat.focus()
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Horizontal(classes="chat-item"):
|
"""Компонуем виджет чата"""
|
||||||
# Используем ASCII-символы для рамки
|
with Horizontal():
|
||||||
yield Label(f"+---+\n| {safe_ascii(self.username[:1].upper()):1} |\n+---+")
|
# Аватар (первая буква имени)
|
||||||
with Vertical():
|
first_letter = normalize_text(self._username[:1].upper()) or "?"
|
||||||
yield Label(normalize_text(self.username), id="name")
|
yield Static(first_letter, classes="chat-avatar")
|
||||||
yield Label(normalize_text(self.msg), id="last_msg")
|
|
||||||
|
# Контент (имя и сообщение)
|
||||||
def on_mouse_enter(self) -> None:
|
with Vertical(classes="chat-content"):
|
||||||
self.add_class("hover")
|
name = normalize_text(self._username) or "Без названия"
|
||||||
|
msg = normalize_text(self._msg) or "Нет сообщений"
|
||||||
def on_mouse_leave(self) -> None:
|
msg = msg[:50] + "..." if len(msg) > 50 else msg
|
||||||
self.remove_class("hover")
|
|
||||||
|
yield Static(name, classes="chat-name")
|
||||||
|
yield Static(msg, classes="chat-message")
|
||||||
|
|
||||||
class Dialog(Widget):
|
class Dialog(Widget):
|
||||||
"""Класс окна диалога"""
|
"""Класс окна диалога"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user