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:
54
src/media/image_renderer.rs
Normal file
54
src/media/image_renderer.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Terminal image renderer using ratatui-image.
|
||||
//!
|
||||
//! Detects terminal protocol (iTerm2, Sixel, Halfblocks) and renders images
|
||||
//! as StatefulProtocol widgets.
|
||||
|
||||
use crate::types::MessageId;
|
||||
use ratatui_image::picker::Picker;
|
||||
use ratatui_image::protocol::StatefulProtocol;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Рендерер изображений для терминала
|
||||
pub struct ImageRenderer {
|
||||
picker: Picker,
|
||||
/// Протоколы рендеринга для каждого сообщения (message_id -> protocol)
|
||||
protocols: HashMap<i64, StatefulProtocol>,
|
||||
}
|
||||
|
||||
impl ImageRenderer {
|
||||
/// Создаёт новый ImageRenderer, определяя поддерживаемый протокол терминала
|
||||
pub fn new() -> Option<Self> {
|
||||
let picker = Picker::from_query_stdio().ok()?;
|
||||
Some(Self {
|
||||
picker,
|
||||
protocols: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Загружает изображение из файла и создаёт протокол рендеринга
|
||||
pub fn load_image(&mut self, msg_id: MessageId, path: &str) -> Result<(), String> {
|
||||
let img = image::ImageReader::open(path)
|
||||
.map_err(|e| format!("Ошибка открытия: {}", e))?
|
||||
.decode()
|
||||
.map_err(|e| format!("Ошибка декодирования: {}", e))?;
|
||||
|
||||
let protocol = self.picker.new_resize_protocol(img);
|
||||
self.protocols.insert(msg_id.as_i64(), protocol);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Получает мутабельную ссылку на протокол для рендеринга
|
||||
pub fn get_protocol(&mut self, msg_id: &MessageId) -> Option<&mut StatefulProtocol> {
|
||||
self.protocols.get_mut(&msg_id.as_i64())
|
||||
}
|
||||
|
||||
/// Удаляет протокол для сообщения
|
||||
pub fn remove(&mut self, msg_id: &MessageId) {
|
||||
self.protocols.remove(&msg_id.as_i64());
|
||||
}
|
||||
|
||||
/// Очищает все протоколы
|
||||
pub fn clear(&mut self) {
|
||||
self.protocols.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user