This commit is contained in:
Mikhail Kilin
2026-01-22 15:26:15 +03:00
parent 1ef341d907
commit c18f43664e
10 changed files with 436 additions and 87 deletions

View File

@@ -11,6 +11,8 @@ use crossterm::{
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tdlib_rs::enums::Update;
@@ -58,16 +60,22 @@ async fn run_app<B: ratatui::backend::Backend>(
terminal: &mut Terminal<B>,
app: &mut App,
) -> io::Result<()> {
// Флаг для остановки polling задачи
let should_stop = Arc::new(AtomicBool::new(false));
let should_stop_clone = should_stop.clone();
// Канал для передачи updates из polling задачи в main loop
let (update_tx, mut update_rx) = tokio::sync::mpsc::unbounded_channel::<Update>();
// Запускаем polling TDLib receive() в отдельной задаче
tokio::spawn(async move {
loop {
// receive() блокирующий, поэтому запускаем в blocking thread
let polling_handle = tokio::spawn(async move {
while !should_stop_clone.load(Ordering::Relaxed) {
// receive() с таймаутом 0.1 сек чтобы периодически проверять флаг
let result = tokio::task::spawn_blocking(|| tdlib_rs::receive()).await;
if let Ok(Some((update, _client_id))) = result {
let _ = update_tx.send(update);
if update_tx.send(update).is_err() {
break; // Канал закрыт, выходим
}
}
}
});
@@ -100,8 +108,15 @@ async fn run_app<B: ratatui::backend::Backend>(
loop {
// Обрабатываем updates от TDLib из канала (неблокирующе)
let mut had_updates = false;
while let Ok(update) = update_rx.try_recv() {
app.td_client.handle_update(update);
had_updates = true;
}
// Помечаем UI как требующий перерисовки если были обновления
if had_updates {
app.needs_redraw = true;
}
// Обрабатываем очередь сообщений для отметки как прочитанных
@@ -116,6 +131,7 @@ async fn run_app<B: ratatui::backend::Backend>(
// Синхронизируем сообщения из td_client в app (для новых сообщений в реальном времени)
if app.selected_chat_id.is_some() && !app.td_client.current_chat_messages.is_empty() {
let prev_messages_len = app.current_messages.len();
// Синхронизируем все сообщения (включая обновлённые имена и is_read)
for td_msg in &app.td_client.current_chat_messages {
if let Some(app_msg) = app.current_messages.iter_mut().find(|m| m.id == td_msg.id) {
@@ -127,37 +143,75 @@ async fn run_app<B: ratatui::backend::Backend>(
app.current_messages.push(td_msg.clone());
}
}
// Если добавились новые сообщения - нужна перерисовка
if app.current_messages.len() != prev_messages_len {
app.needs_redraw = true;
}
}
// Обновляем состояние экрана на основе auth_state
update_screen_state(app).await;
let screen_changed = update_screen_state(app).await;
if screen_changed {
app.needs_redraw = true;
}
terminal.draw(|f| ui::render(f, app))?;
// Рендерим только если есть изменения
if app.needs_redraw {
terminal.draw(|f| ui::render(f, app))?;
app.needs_redraw = false;
}
// Используем poll для неблокирующего чтения событий
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
// Global quit command
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
return Ok(());
}
// Используем poll с коротким таймаутом для быстрой реакции на ввод
// 16ms ≈ 60 FPS потенциально, но рендерим только при изменениях
if event::poll(Duration::from_millis(16))? {
match event::read()? {
Event::Key(key) => {
// Global quit command
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
// Graceful shutdown
should_stop.store(true, Ordering::Relaxed);
match app.screen {
AppScreen::Loading => {
// В состоянии загрузки игнорируем ввод
// Закрываем TDLib клиент
let _ = tdlib_rs::functions::close(app.td_client.client_id()).await;
// Ждём завершения polling задачи (с таймаутом)
let _ = tokio::time::timeout(
Duration::from_secs(2),
polling_handle
).await;
return Ok(());
}
AppScreen::Auth => handle_auth_input(app, key.code).await,
AppScreen::Main => handle_main_input(app, key).await,
match app.screen {
AppScreen::Loading => {
// В состоянии загрузки игнорируем ввод
}
AppScreen::Auth => handle_auth_input(app, key.code).await,
AppScreen::Main => handle_main_input(app, key).await,
}
// Любой ввод требует перерисовки
app.needs_redraw = true;
}
Event::Resize(_, _) => {
// При изменении размера терминала нужна перерисовка
app.needs_redraw = true;
}
_ => {}
}
}
}
}
async fn update_screen_state(app: &mut App) {
/// Возвращает true если состояние изменилось и требуется перерисовка
async fn update_screen_state(app: &mut App) -> bool {
use tokio::time::timeout;
let prev_screen = app.screen.clone();
let prev_status = app.status_message.clone();
let prev_error = app.error_message.clone();
let prev_chats_len = app.chats.len();
match &app.td_client.auth_state {
AuthState::WaitTdlibParameters => {
@@ -198,4 +252,10 @@ async fn update_screen_state(app: &mut App) {
app.error_message = Some(e.clone());
}
}
// Проверяем, изменилось ли что-то
app.screen != prev_screen
|| app.status_message != prev_status
|| app.error_message != prev_error
|| app.chats.len() != prev_chats_len
}