diff --git a/src/app/methods/navigation.rs b/src/app/methods/navigation.rs index a9ad35d..bec7dba 100644 --- a/src/app/methods/navigation.rs +++ b/src/app/methods/navigation.rs @@ -87,6 +87,7 @@ impl NavigationMethods for App { #[cfg(feature = "images")] { self.photo_download_rx = None; + self.pending_image_open = None; } // Сбрасываем состояние чата в нормальный режим self.chat_state = ChatState::Normal; diff --git a/src/app/mod.rs b/src/app/mod.rs index 6e6d76f..f522410 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -20,6 +20,19 @@ use crate::types::ChatId; use ratatui::widgets::ListState; use std::path::PathBuf; +/// Pending intent to open the image modal once a photo finishes downloading. +/// +/// Set when the user presses `v` on a photo that is still downloading. +/// The main loop opens the modal automatically when the download completes. +#[cfg(feature = "images")] +#[derive(Debug, Clone)] +pub struct PendingImageOpen { + pub file_id: i32, + pub message_id: crate::types::MessageId, + pub photo_width: i32, + pub photo_height: i32, +} + /// State of the account switcher modal overlay. #[derive(Debug, Clone)] pub enum AccountSwitcherState { @@ -123,6 +136,9 @@ pub struct App { /// Время последнего рендеринга изображений (для throttling до 15 FPS) #[cfg(feature = "images")] pub last_image_render_time: Option, + /// Pending intent: открыть модалку для этого фото когда загрузится + #[cfg(feature = "images")] + pub pending_image_open: Option, // Account lock /// Advisory file lock to prevent concurrent access to the same account pub account_lock: Option, @@ -219,6 +235,8 @@ impl App { image_modal: None, #[cfg(feature = "images")] last_image_render_time: None, + #[cfg(feature = "images")] + pending_image_open: None, // Voice playback audio_player: crate::audio::AudioPlayer::new().ok(), voice_cache: crate::audio::VoiceCache::new(audio_cache_size_mb).ok(), diff --git a/src/input/handlers/chat.rs b/src/input/handlers/chat.rs index e3c56a3..517a265 100644 --- a/src/input/handlers/chat.rs +++ b/src/input/handlers/chat.rs @@ -584,45 +584,44 @@ async fn handle_view_image(app: &mut App) { }); app.needs_redraw = true; } - PhotoDownloadState::Downloading => { - app.status_message = Some("Загрузка фото...".to_string()); - } - PhotoDownloadState::NotDownloaded => { - // Скачиваем фото и открываем + PhotoDownloadState::NotDownloaded | PhotoDownloadState::Downloading => { + // Запоминаем намерение открыть модалку — откроется когда загрузится + app.pending_image_open = Some(crate::app::PendingImageOpen { + file_id, + message_id: msg_id, + photo_width, + photo_height, + }); app.status_message = Some("Загрузка фото...".to_string()); app.needs_redraw = true; - match app.td_client.download_file(file_id).await { - Ok(path) => { - // Обновляем состояние загрузки в сообщении - for msg in app.td_client.current_chat_messages_mut() { - if let Some(photo) = msg.photo_info_mut() { - if photo.file_id == file_id { - photo.download_state = PhotoDownloadState::Downloaded(path.clone()); - break; + + // Если нет активной фоновой загрузки — запускаем свою + if app.photo_download_rx.is_none() { + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + app.photo_download_rx = Some(rx); + let client_id = app.td_client.client_id(); + tokio::spawn(async move { + let result = tokio::time::timeout(Duration::from_secs(30), async { + match tdlib_rs::functions::download_file( + file_id, 1, 0, 0, true, client_id, + ) + .await + { + Ok(tdlib_rs::enums::File::File(f)) + if f.local.is_downloading_completed + && !f.local.path.is_empty() => + { + Ok(f.local.path) } + Ok(_) => Err("Файл не скачан".to_string()), + Err(e) => Err(format!("{:?}", e)), } - } - // Открываем модалку - app.image_modal = Some(ImageModalState { - message_id: msg_id, - photo_path: path, - photo_width, - photo_height, - }); - app.status_message = None; - } - Err(e) => { - for msg in app.td_client.current_chat_messages_mut() { - if let Some(photo) = msg.photo_info_mut() { - if photo.file_id == file_id { - photo.download_state = PhotoDownloadState::Error(e.clone()); - break; - } - } - } - app.error_message = Some(format!("Ошибка загрузки фото: {}", e)); - app.status_message = None; - } + }) + .await; + let result = + result.unwrap_or_else(|_| Err("Таймаут загрузки".to_string())); + let _ = tx.send((file_id, result)); + }); } } PhotoDownloadState::Error(_) => { diff --git a/src/main.rs b/src/main.rs index 19e92a1..a223ff7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -216,6 +216,47 @@ async fn run_app( } } } + // Если это фото ждёт открытия в модалке — открываем + let pending_matches = app + .pending_image_open + .as_ref() + .map(|p| p.file_id == file_id) + .unwrap_or(false); + if pending_matches { + // Ищем путь из обновлённого состояния + let downloaded_path = app + .td_client + .current_chat_messages() + .iter() + .find_map(|m| { + m.photo_info().and_then(|p| { + if p.file_id == file_id { + if let PhotoDownloadState::Downloaded(ref path) = + p.download_state + { + Some(path.clone()) + } else { + None + } + } else { + None + } + }) + }); + if let (Some(path), Some(pending)) = + (downloaded_path, app.pending_image_open.take()) + { + use crate::tdlib::ImageModalState; + app.image_modal = Some(ImageModalState { + message_id: pending.message_id, + photo_path: path, + photo_width: pending.photo_width, + photo_height: pending.photo_height, + }); + app.status_message = None; + got_photos = true; + } + } } } if got_photos {