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:
Mikhail Kilin
2026-02-06 21:25:17 +03:00
parent 6845ee69bf
commit b0f1f9fdc2
29 changed files with 1505 additions and 102 deletions

View File

@@ -48,6 +48,9 @@ pub enum Command {
ReactMessage,
SelectMessage,
// Media
ViewImage,
// Input
SubmitMessage,
Cancel,
@@ -201,7 +204,13 @@ impl Keybindings {
KeyBinding::new(KeyCode::Char('у')), // RU
]);
// Note: SelectMessage обрабатывается через Command::SubmitMessage в handle_enter_key()
// Media
bindings.insert(Command::ViewImage, vec![
KeyBinding::new(KeyCode::Char('v')),
KeyBinding::new(KeyCode::Char('м')), // RU
]);
// Input
bindings.insert(Command::SubmitMessage, vec![
KeyBinding::new(KeyCode::Enter),

View File

@@ -43,6 +43,10 @@ pub struct Config {
/// Настройки desktop notifications.
#[serde(default)]
pub notifications: NotificationsConfig,
/// Настройки отображения изображений.
#[serde(default)]
pub images: ImagesConfig,
}
/// Общие настройки приложения.
@@ -105,6 +109,27 @@ pub struct NotificationsConfig {
pub urgency: String,
}
/// Настройки отображения изображений.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImagesConfig {
/// Показывать превью изображений в чате
#[serde(default = "default_show_images")]
pub show_images: bool,
/// Размер кэша изображений (в МБ)
#[serde(default = "default_image_cache_size_mb")]
pub cache_size_mb: u64,
}
impl Default for ImagesConfig {
fn default() -> Self {
Self {
show_images: default_show_images(),
cache_size_mb: default_image_cache_size_mb(),
}
}
}
// Дефолтные значения (используются serde атрибутами)
fn default_timezone() -> String {
"+03:00".to_string()
@@ -146,6 +171,14 @@ fn default_notification_urgency() -> String {
"normal".to_string()
}
fn default_show_images() -> bool {
true
}
fn default_image_cache_size_mb() -> u64 {
crate::constants::DEFAULT_IMAGE_CACHE_SIZE_MB
}
impl Default for GeneralConfig {
fn default() -> Self {
Self { timezone: default_timezone() }
@@ -183,6 +216,7 @@ impl Default for Config {
colors: ColorsConfig::default(),
keybindings: Keybindings::default(),
notifications: NotificationsConfig::default(),
images: ImagesConfig::default(),
}
}
}