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:
93
src/main.rs
93
src/main.rs
@@ -182,6 +182,34 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
|
||||
// Обрабатываем результаты фоновой загрузки фото
|
||||
#[cfg(feature = "images")]
|
||||
{
|
||||
use crate::tdlib::PhotoDownloadState;
|
||||
|
||||
let mut got_photos = false;
|
||||
if let Some(ref mut rx) = app.photo_download_rx {
|
||||
while let Ok((file_id, result)) = rx.try_recv() {
|
||||
let new_state = match result {
|
||||
Ok(path) => PhotoDownloadState::Downloaded(path),
|
||||
Err(_) => PhotoDownloadState::Error("Ошибка загрузки".to_string()),
|
||||
};
|
||||
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 = new_state;
|
||||
got_photos = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if got_photos {
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Очищаем устаревший typing status
|
||||
if app.td_client.clear_stale_typing_status() {
|
||||
app.needs_redraw = true;
|
||||
@@ -315,7 +343,7 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
)
|
||||
.await;
|
||||
|
||||
// Авто-загрузка фото (последние 30 сообщений)
|
||||
// Авто-загрузка фото — неблокирующая фоновая задача (до 5 фото параллельно)
|
||||
#[cfg(feature = "images")]
|
||||
{
|
||||
use crate::tdlib::PhotoDownloadState;
|
||||
@@ -326,7 +354,7 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.rev()
|
||||
.take(30)
|
||||
.take(5)
|
||||
.filter_map(|msg| {
|
||||
msg.photo_info().and_then(|p| {
|
||||
matches!(p.download_state, PhotoDownloadState::NotDownloaded)
|
||||
@@ -335,22 +363,42 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
})
|
||||
.collect();
|
||||
|
||||
for file_id in &photo_file_ids {
|
||||
if let Ok(Ok(path)) = tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.download_file(*file_id),
|
||||
)
|
||||
.await
|
||||
{
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !photo_file_ids.is_empty() {
|
||||
let client_id = app.td_client.client_id();
|
||||
let (tx, rx) =
|
||||
tokio::sync::mpsc::unbounded_channel::<(i32, Result<String, String>)>();
|
||||
app.photo_download_rx = Some(rx);
|
||||
|
||||
for file_id in photo_file_ids {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
async {
|
||||
match tdlib_rs::functions::download_file(
|
||||
file_id, 1, 0, 0, true, client_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(tdlib_rs::enums::File::File(file))
|
||||
if file.local.is_downloading_completed
|
||||
&& !file.local.path.is_empty() =>
|
||||
{
|
||||
Ok(file.local.path)
|
||||
}
|
||||
Ok(_) => Err("Файл не скачан".to_string()),
|
||||
Err(e) => Err(format!("{:?}", e)),
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let result = match result {
|
||||
Ok(r) => r,
|
||||
Err(_) => Err("Таймаут загрузки".to_string()),
|
||||
};
|
||||
let _ = tx.send((file_id, result));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,8 +419,15 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
||||
}
|
||||
|
||||
// 3. Reset app state
|
||||
app.current_account_name = account_name;
|
||||
app.current_account_name = account_name.clone();
|
||||
app.screen = AppScreen::Loading;
|
||||
|
||||
// 4. Persist selected account as default for next launch
|
||||
let mut accounts_config = accounts::load_or_create();
|
||||
accounts_config.default_account = account_name;
|
||||
if let Err(e) = accounts::save(&accounts_config) {
|
||||
tracing::warn!("Could not save default account: {}", e);
|
||||
}
|
||||
app.chats.clear();
|
||||
app.selected_chat_id = None;
|
||||
app.chat_state = Default::default();
|
||||
|
||||
Reference in New Issue
Block a user