feat: implement photo albums (media groups) and persist account selection

Group photos with shared media_album_id into single album bubbles with
grid layout (up to 3x cols). Album navigation treats grouped photos as
one unit (j/k skip entire album). Persist selected account to
accounts.toml so it survives app restart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-22 16:18:04 +03:00
parent 8bd08318bb
commit 78fe09bf11
18 changed files with 1011 additions and 30 deletions

View File

@@ -251,6 +251,7 @@ fn render_message_list<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut Ap
message_id: msg.id(),
photo_path: path.clone(),
line_offset: placeholder_start,
x_offset: 0,
width: img_width,
height: img_height,
});
@@ -259,6 +260,48 @@ fn render_message_list<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut Ap
lines.extend(bubble_lines);
}
MessageGroup::Album(album_messages) => {
#[cfg(feature = "images")]
{
let is_selected = album_messages
.iter()
.any(|m| selected_msg_id == Some(m.id()));
if is_selected {
selected_msg_line = Some(lines.len());
}
let (bubble_lines, album_deferred) = components::render_album_bubble(
&album_messages,
app.config(),
content_width,
selected_msg_id,
);
for mut d in album_deferred {
d.line_offset += lines.len();
deferred_images.push(d);
}
lines.extend(bubble_lines);
}
#[cfg(not(feature = "images"))]
{
// Fallback: рендерим каждое сообщение отдельно
for msg in &album_messages {
let is_selected = selected_msg_id == Some(msg.id());
if is_selected {
selected_msg_line = Some(lines.len());
}
lines.extend(components::render_message_bubble(
msg,
app.config(),
content_width,
selected_msg_id,
app.playback_state.as_ref(),
));
}
}
}
}
}
@@ -334,7 +377,7 @@ fn render_message_list<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut Ap
}
// Рендерим с ПОЛНОЙ высотой (не сжимаем)
let img_rect = Rect::new(content_x, img_y, d.width, d.height);
let img_rect = Rect::new(content_x + d.x_offset, img_y, d.width, d.height);
// ОПТИМИЗАЦИЯ: Загружаем только видимые изображения (не все сразу)
// Используем inline_renderer с Halfblocks для скорости