feat: implement Phase 11 — inline photo viewing with ratatui-image
Add feature-gated (`images`) inline photo support: - New types: MediaInfo, PhotoInfo, PhotoDownloadState, ImagesConfig - Media module: ImageCache (LRU filesystem cache), ImageRenderer (terminal protocol detection) - Photo metadata extraction from TDLib MessagePhoto with download_file() API - ViewImage command (v/м) to toggle photo expand/collapse in message selection - Two-pass UI rendering: placeholder lines in message bubbles + StatefulImage overlay - Collapse all expanded photos on Esc (exit selection mode) Dependencies: ratatui-image 8.1, image 0.25 (optional, behind `images` feature flag) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ use crate::types::MessageId;
|
||||
use tdlib_rs::enums::{MessageContent, MessageSender};
|
||||
use tdlib_rs::types::Message as TdMessage;
|
||||
|
||||
use super::types::{ForwardInfo, ReactionInfo, ReplyInfo};
|
||||
use super::types::{ForwardInfo, MediaInfo, PhotoDownloadState, PhotoInfo, ReactionInfo, ReplyInfo};
|
||||
|
||||
/// Извлекает текст контента из TDLib Message
|
||||
///
|
||||
@@ -19,9 +19,9 @@ pub fn extract_content_text(msg: &TdMessage) -> String {
|
||||
MessageContent::MessagePhoto(p) => {
|
||||
let caption_text = p.caption.text.clone();
|
||||
if caption_text.is_empty() {
|
||||
"[Фото]".to_string()
|
||||
"📷 [Фото]".to_string()
|
||||
} else {
|
||||
caption_text
|
||||
format!("📷 {}", caption_text)
|
||||
}
|
||||
}
|
||||
MessageContent::MessageVideo(v) => {
|
||||
@@ -132,6 +132,40 @@ pub fn extract_reply_info(msg: &TdMessage) -> Option<ReplyInfo> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Извлекает информацию о медиа-контенте из TDLib Message
|
||||
///
|
||||
/// Для MessagePhoto: получает лучший размер фото, извлекает file_id, width, height.
|
||||
/// Возвращает None для не-медийных типов сообщений.
|
||||
pub fn extract_media_info(msg: &TdMessage) -> Option<MediaInfo> {
|
||||
match &msg.content {
|
||||
MessageContent::MessagePhoto(p) => {
|
||||
// Берём лучший (последний = самый большой) размер фото
|
||||
let best_size = p.photo.sizes.last()?;
|
||||
let file_id = best_size.photo.id;
|
||||
let width = best_size.width;
|
||||
let height = best_size.height;
|
||||
|
||||
// Проверяем, скачан ли файл
|
||||
let download_state = if !best_size.photo.local.path.is_empty()
|
||||
&& best_size.photo.local.is_downloading_completed
|
||||
{
|
||||
PhotoDownloadState::Downloaded(best_size.photo.local.path.clone())
|
||||
} else {
|
||||
PhotoDownloadState::NotDownloaded
|
||||
};
|
||||
|
||||
Some(MediaInfo::Photo(PhotoInfo {
|
||||
file_id,
|
||||
width,
|
||||
height,
|
||||
download_state,
|
||||
expanded: false,
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Извлекает реакции из TDLib Message
|
||||
pub fn extract_reactions(msg: &TdMessage) -> Vec<ReactionInfo> {
|
||||
msg.interaction_info
|
||||
|
||||
Reference in New Issue
Block a user