Merge pull request 'refactor' (#24) from refactor into main
Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
@@ -164,7 +164,5 @@
|
|||||||
- `pending_account_switch` флаг → обработка в main loop
|
- `pending_account_switch` флаг → обработка в main loop
|
||||||
|
|
||||||
- [ ] **Этап 4: Расширенные возможности мультиаккаунта**
|
- [ ] **Этап 4: Расширенные возможности мультиаккаунта**
|
||||||
- Удаление аккаунта из модалки
|
|
||||||
- Хоткеи `Ctrl+1`..`Ctrl+9` — быстрое переключение
|
- Хоткеи `Ctrl+1`..`Ctrl+9` — быстрое переключение
|
||||||
- Бейджи непрочитанных с других аккаунтов (требует множественных TdClient)
|
- Бейджи непрочитанных с других аккаунтов (требует множественных TdClient)
|
||||||
- Параллельный polling updates со всех аккаунтов
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! - Editing and sending messages
|
//! - Editing and sending messages
|
||||||
//! - Loading older messages
|
//! - Loading older messages
|
||||||
|
|
||||||
use super::chat_list::open_chat_and_load_data;
|
use super::chat_loader::{load_older_messages_if_needed, open_chat_and_load_data};
|
||||||
use crate::app::methods::{
|
use crate::app::methods::{
|
||||||
compose::ComposeMethods, messages::MessageMethods, modal::ModalMethods,
|
compose::ComposeMethods, messages::MessageMethods, modal::ModalMethods,
|
||||||
navigation::NavigationMethods,
|
navigation::NavigationMethods,
|
||||||
@@ -16,7 +16,7 @@ use crate::app::InputMode;
|
|||||||
use crate::input::handlers::{copy_to_clipboard, format_message_for_clipboard};
|
use crate::input::handlers::{copy_to_clipboard, format_message_for_clipboard};
|
||||||
use crate::tdlib::{ChatAction, TdClientTrait};
|
use crate::tdlib::{ChatAction, TdClientTrait};
|
||||||
use crate::types::{ChatId, MessageId};
|
use crate::types::{ChatId, MessageId};
|
||||||
use crate::utils::{is_non_empty, with_timeout, with_timeout_msg};
|
use crate::utils::{is_non_empty, with_timeout_msg};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
@@ -340,50 +340,6 @@ pub async fn send_reaction<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Подгружает старые сообщения если скролл близко к верху
|
|
||||||
pub async fn load_older_messages_if_needed<T: TdClientTrait>(app: &mut App<T>) {
|
|
||||||
// Check if there are messages to load from
|
|
||||||
if app.td_client.current_chat_messages().is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the oldest message ID
|
|
||||||
let oldest_msg_id = app
|
|
||||||
.td_client
|
|
||||||
.current_chat_messages()
|
|
||||||
.first()
|
|
||||||
.map(|m| m.id())
|
|
||||||
.unwrap_or(MessageId::new(0));
|
|
||||||
|
|
||||||
// Get current chat ID
|
|
||||||
let Some(chat_id) = app.get_selected_chat_id() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if scroll is near the top
|
|
||||||
let message_count = app.td_client.current_chat_messages().len();
|
|
||||||
if app.message_scroll_offset <= message_count.saturating_sub(10) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load older messages with timeout
|
|
||||||
let Ok(older) = with_timeout(
|
|
||||||
Duration::from_secs(3),
|
|
||||||
app.td_client
|
|
||||||
.load_older_messages(ChatId::new(chat_id), oldest_msg_id),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add older messages to the beginning if any were loaded
|
|
||||||
if !older.is_empty() {
|
|
||||||
let msgs = app.td_client.current_chat_messages_mut();
|
|
||||||
msgs.splice(0..0, older);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Обработка ввода клавиатуры в открытом чате
|
/// Обработка ввода клавиатуры в открытом чате
|
||||||
///
|
///
|
||||||
/// Обрабатывает:
|
/// Обрабатывает:
|
||||||
|
|||||||
@@ -5,14 +5,10 @@
|
|||||||
//! - Folder selection
|
//! - Folder selection
|
||||||
//! - Opening chats
|
//! - Opening chats
|
||||||
|
|
||||||
use crate::app::methods::{
|
use crate::app::methods::navigation::NavigationMethods;
|
||||||
compose::ComposeMethods, messages::MessageMethods, navigation::NavigationMethods,
|
|
||||||
};
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::InputMode;
|
|
||||||
use crate::tdlib::TdClientTrait;
|
use crate::tdlib::TdClientTrait;
|
||||||
use crate::types::{ChatId, MessageId};
|
use crate::utils::with_timeout;
|
||||||
use crate::utils::{with_timeout, with_timeout_msg};
|
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -79,62 +75,3 @@ pub async fn select_folder<T: TdClientTrait>(app: &mut App<T>, folder_idx: usize
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Открывает чат и загружает последние сообщения (быстро).
|
|
||||||
///
|
|
||||||
/// Загружает только 50 последних сообщений для мгновенного отображения.
|
|
||||||
/// Фоновые задачи (reply info, pinned, photos) откладываются в `pending_chat_init`
|
|
||||||
/// и выполняются на следующем тике main loop.
|
|
||||||
///
|
|
||||||
/// При ошибке устанавливает error_message и очищает status_message.
|
|
||||||
pub async fn open_chat_and_load_data<T: TdClientTrait>(app: &mut App<T>, chat_id: i64) {
|
|
||||||
app.status_message = Some("Загрузка сообщений...".to_string());
|
|
||||||
app.message_scroll_offset = 0;
|
|
||||||
|
|
||||||
// Загружаем только 50 последних сообщений (один запрос к TDLib)
|
|
||||||
match with_timeout_msg(
|
|
||||||
Duration::from_secs(10),
|
|
||||||
app.td_client.get_chat_history(ChatId::new(chat_id), 50),
|
|
||||||
"Таймаут загрузки сообщений",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(messages) => {
|
|
||||||
// Собираем ID всех входящих сообщений для отметки как прочитанные
|
|
||||||
let incoming_message_ids: Vec<MessageId> = messages
|
|
||||||
.iter()
|
|
||||||
.filter(|msg| !msg.is_outgoing())
|
|
||||||
.map(|msg| msg.id())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Сохраняем загруженные сообщения
|
|
||||||
app.td_client.set_current_chat_messages(messages);
|
|
||||||
|
|
||||||
// Добавляем входящие сообщения в очередь для отметки как прочитанные
|
|
||||||
if !incoming_message_ids.is_empty() {
|
|
||||||
app.td_client
|
|
||||||
.pending_view_messages_mut()
|
|
||||||
.push((ChatId::new(chat_id), incoming_message_ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории
|
|
||||||
// Это предотвращает race condition с Update::NewMessage
|
|
||||||
app.td_client
|
|
||||||
.set_current_chat_id(Some(ChatId::new(chat_id)));
|
|
||||||
|
|
||||||
// Загружаем черновик (локальная операция, мгновенно)
|
|
||||||
app.load_draft();
|
|
||||||
|
|
||||||
// Показываем чат СРАЗУ
|
|
||||||
app.status_message = None;
|
|
||||||
app.input_mode = InputMode::Normal;
|
|
||||||
app.start_message_selection();
|
|
||||||
|
|
||||||
// Фоновые задачи (reply info, pinned, photos) — на следующем тике main loop
|
|
||||||
app.pending_chat_init = Some(ChatId::new(chat_id));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
app.error_message = Some(e);
|
|
||||||
app.status_message = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
194
src/input/handlers/chat_loader.rs
Normal file
194
src/input/handlers/chat_loader.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
//! Chat loading logic — all three phases of message loading
|
||||||
|
//!
|
||||||
|
//! - Phase 1: `open_chat_and_load_data` — fast initial load (50 messages)
|
||||||
|
//! - Phase 2: `process_pending_chat_init` — background tasks (reply info, pinned, photos)
|
||||||
|
//! - Phase 3: `load_older_messages_if_needed` — lazy load on scroll up
|
||||||
|
|
||||||
|
use crate::app::methods::{compose::ComposeMethods, messages::MessageMethods};
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::InputMode;
|
||||||
|
use crate::tdlib::TdClientTrait;
|
||||||
|
use crate::types::{ChatId, MessageId};
|
||||||
|
use crate::utils::{with_timeout, with_timeout_ignore, with_timeout_msg};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Открывает чат и загружает последние сообщения (быстро).
|
||||||
|
///
|
||||||
|
/// Загружает только 50 последних сообщений для мгновенного отображения.
|
||||||
|
/// Фоновые задачи (reply info, pinned, photos) откладываются в `pending_chat_init`
|
||||||
|
/// и выполняются на следующем тике main loop.
|
||||||
|
///
|
||||||
|
/// При ошибке устанавливает error_message и очищает status_message.
|
||||||
|
pub async fn open_chat_and_load_data<T: TdClientTrait>(app: &mut App<T>, chat_id: i64) {
|
||||||
|
app.status_message = Some("Загрузка сообщений...".to_string());
|
||||||
|
app.message_scroll_offset = 0;
|
||||||
|
|
||||||
|
// Загружаем только 50 последних сообщений (один запрос к TDLib)
|
||||||
|
match with_timeout_msg(
|
||||||
|
Duration::from_secs(10),
|
||||||
|
app.td_client.get_chat_history(ChatId::new(chat_id), 50),
|
||||||
|
"Таймаут загрузки сообщений",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(messages) => {
|
||||||
|
// Собираем ID всех входящих сообщений для отметки как прочитанные
|
||||||
|
let incoming_message_ids: Vec<MessageId> = messages
|
||||||
|
.iter()
|
||||||
|
.filter(|msg| !msg.is_outgoing())
|
||||||
|
.map(|msg| msg.id())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Сохраняем загруженные сообщения
|
||||||
|
app.td_client.set_current_chat_messages(messages);
|
||||||
|
|
||||||
|
// Добавляем входящие сообщения в очередь для отметки как прочитанные
|
||||||
|
if !incoming_message_ids.is_empty() {
|
||||||
|
app.td_client
|
||||||
|
.pending_view_messages_mut()
|
||||||
|
.push((ChatId::new(chat_id), incoming_message_ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории
|
||||||
|
// Это предотвращает race condition с Update::NewMessage
|
||||||
|
app.td_client
|
||||||
|
.set_current_chat_id(Some(ChatId::new(chat_id)));
|
||||||
|
|
||||||
|
// Загружаем черновик (локальная операция, мгновенно)
|
||||||
|
app.load_draft();
|
||||||
|
|
||||||
|
// Показываем чат СРАЗУ
|
||||||
|
app.status_message = None;
|
||||||
|
app.input_mode = InputMode::Normal;
|
||||||
|
app.start_message_selection();
|
||||||
|
|
||||||
|
// Фоновые задачи (reply info, pinned, photos) — на следующем тике main loop
|
||||||
|
app.pending_chat_init = Some(ChatId::new(chat_id));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
app.error_message = Some(e);
|
||||||
|
app.status_message = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Выполняет фоновую инициализацию после открытия чата.
|
||||||
|
///
|
||||||
|
/// Вызывается на следующем тике main loop после `open_chat_and_load_data`.
|
||||||
|
/// Загружает reply info, закреплённое сообщение и начинает авто-загрузку фото.
|
||||||
|
pub async fn process_pending_chat_init<T: TdClientTrait>(app: &mut App<T>, chat_id: ChatId) {
|
||||||
|
// Загружаем недостающие reply info (игнорируем ошибки)
|
||||||
|
with_timeout_ignore(Duration::from_secs(5), app.td_client.fetch_missing_reply_info())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Загружаем последнее закреплённое сообщение (игнорируем ошибки)
|
||||||
|
with_timeout_ignore(
|
||||||
|
Duration::from_secs(2),
|
||||||
|
app.td_client.load_current_pinned_message(chat_id),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Авто-загрузка фото — неблокирующая фоновая задача (до 5 фото параллельно)
|
||||||
|
#[cfg(feature = "images")]
|
||||||
|
{
|
||||||
|
use crate::tdlib::PhotoDownloadState;
|
||||||
|
|
||||||
|
if app.config().images.auto_download_images && app.config().images.show_images {
|
||||||
|
let photo_file_ids: Vec<i32> = app
|
||||||
|
.td_client
|
||||||
|
.current_chat_messages()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.take(5)
|
||||||
|
.filter_map(|msg| {
|
||||||
|
msg.photo_info().and_then(|p| {
|
||||||
|
matches!(p.download_state, PhotoDownloadState::NotDownloaded)
|
||||||
|
.then_some(p.file_id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Подгружает старые сообщения если скролл близко к верху
|
||||||
|
pub async fn load_older_messages_if_needed<T: TdClientTrait>(app: &mut App<T>) {
|
||||||
|
// Check if there are messages to load from
|
||||||
|
if app.td_client.current_chat_messages().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the oldest message ID
|
||||||
|
let oldest_msg_id = app
|
||||||
|
.td_client
|
||||||
|
.current_chat_messages()
|
||||||
|
.first()
|
||||||
|
.map(|m| m.id())
|
||||||
|
.unwrap_or(MessageId::new(0));
|
||||||
|
|
||||||
|
// Get current chat ID
|
||||||
|
let Some(chat_id) = app.get_selected_chat_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if scroll is near the top
|
||||||
|
let message_count = app.td_client.current_chat_messages().len();
|
||||||
|
if app.message_scroll_offset <= message_count.saturating_sub(10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load older messages with timeout
|
||||||
|
let Ok(older) = with_timeout(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
app.td_client
|
||||||
|
.load_older_messages(ChatId::new(chat_id), oldest_msg_id),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add older messages to the beginning if any were loaded
|
||||||
|
if !older.is_empty() {
|
||||||
|
let msgs = app.td_client.current_chat_messages_mut();
|
||||||
|
msgs.splice(0..0, older);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,14 @@
|
|||||||
//! - profile: Profile helper functions
|
//! - profile: Profile helper functions
|
||||||
//! - chat: Keyboard input handling for open chat view
|
//! - chat: Keyboard input handling for open chat view
|
||||||
//! - chat_list: Navigation and interaction in the chat list
|
//! - chat_list: Navigation and interaction in the chat list
|
||||||
|
//! - chat_loader: All phases of chat message loading
|
||||||
//! - compose: Text input, editing, and message composition
|
//! - compose: Text input, editing, and message composition
|
||||||
//! - modal: Modal dialogs (delete confirmation, emoji picker, etc.)
|
//! - modal: Modal dialogs (delete confirmation, emoji picker, etc.)
|
||||||
//! - search: Search functionality (chat search, message search)
|
//! - search: Search functionality (chat search, message search)
|
||||||
|
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod chat_list;
|
pub mod chat_list;
|
||||||
|
pub mod chat_loader;
|
||||||
pub mod clipboard;
|
pub mod clipboard;
|
||||||
pub mod compose;
|
pub mod compose;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
@@ -19,6 +21,7 @@ pub mod modal;
|
|||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
|
||||||
|
pub use chat_loader::{load_older_messages_if_needed, open_chat_and_load_data, process_pending_chat_init};
|
||||||
pub use clipboard::*;
|
pub use clipboard::*;
|
||||||
pub use global::*;
|
pub use global::*;
|
||||||
pub use profile::get_available_actions_count;
|
pub use profile::get_available_actions_count;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::utils::with_timeout;
|
|||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use crossterm::event::{KeyCode, KeyEvent};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::chat_list::open_chat_and_load_data;
|
use super::chat_loader::open_chat_and_load_data;
|
||||||
use super::scroll_to_message;
|
use super::scroll_to_message;
|
||||||
|
|
||||||
/// Обработка режима поиска по чатам
|
/// Обработка режима поиска по чатам
|
||||||
|
|||||||
72
src/main.rs
72
src/main.rs
@@ -29,6 +29,7 @@ use tdlib_rs::enums::Update;
|
|||||||
use app::{App, AppScreen};
|
use app::{App, AppScreen};
|
||||||
use constants::{POLL_TIMEOUT_MS, SHUTDOWN_TIMEOUT_SECS};
|
use constants::{POLL_TIMEOUT_MS, SHUTDOWN_TIMEOUT_SECS};
|
||||||
use input::{handle_auth_input, handle_main_input};
|
use input::{handle_auth_input, handle_main_input};
|
||||||
|
use input::handlers::process_pending_chat_init;
|
||||||
use tdlib::AuthState;
|
use tdlib::AuthState;
|
||||||
use utils::{disable_tdlib_logs, with_timeout_ignore};
|
use utils::{disable_tdlib_logs, with_timeout_ignore};
|
||||||
|
|
||||||
@@ -345,76 +346,7 @@ async fn run_app<B: ratatui::backend::Backend, T: tdlib::TdClientTrait>(
|
|||||||
|
|
||||||
// Process pending chat initialization (reply info, pinned, photos)
|
// Process pending chat initialization (reply info, pinned, photos)
|
||||||
if let Some(chat_id) = app.pending_chat_init.take() {
|
if let Some(chat_id) = app.pending_chat_init.take() {
|
||||||
// Загружаем недостающие reply info (игнорируем ошибки)
|
process_pending_chat_init(app, chat_id).await;
|
||||||
with_timeout_ignore(Duration::from_secs(5), app.td_client.fetch_missing_reply_info())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Загружаем последнее закреплённое сообщение (игнорируем ошибки)
|
|
||||||
with_timeout_ignore(
|
|
||||||
Duration::from_secs(2),
|
|
||||||
app.td_client.load_current_pinned_message(chat_id),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Авто-загрузка фото — неблокирующая фоновая задача (до 5 фото параллельно)
|
|
||||||
#[cfg(feature = "images")]
|
|
||||||
{
|
|
||||||
use crate::tdlib::PhotoDownloadState;
|
|
||||||
|
|
||||||
if app.config().images.auto_download_images && app.config().images.show_images {
|
|
||||||
let photo_file_ids: Vec<i32> = app
|
|
||||||
.td_client
|
|
||||||
.current_chat_messages()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.take(5)
|
|
||||||
.filter_map(|msg| {
|
|
||||||
msg.photo_info().and_then(|p| {
|
|
||||||
matches!(p.download_state, PhotoDownloadState::NotDownloaded)
|
|
||||||
.then_some(p.file_id)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.needs_redraw = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check pending account switch
|
// Check pending account switch
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &mut App<T>) {
|
|||||||
let search_style = if app.is_searching {
|
let search_style = if app.is_searching {
|
||||||
Style::default().fg(Color::Yellow)
|
Style::default().fg(Color::Yellow)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::DarkGray)
|
Style::default().fg(Color::Rgb(160, 160, 160))
|
||||||
};
|
};
|
||||||
let search = Paragraph::new(search_text)
|
let search = Paragraph::new(search_text)
|
||||||
.block(Block::default().borders(Borders::ALL))
|
.block(Block::default().borders(Borders::ALL))
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
|
|||||||
NetworkState::Updating => "⏳ Обновление... | ",
|
NetworkState::Updating => "⏳ Обновление... | ",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Account indicator (shown if not "default")
|
let account_indicator = format!("[{}] ", app.current_account_name);
|
||||||
let account_indicator = if app.current_account_name != "default" {
|
|
||||||
format!("[{}] ", app.current_account_name)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let status = if let Some(msg) = &app.status_message {
|
let status = if let Some(msg) = &app.status_message {
|
||||||
format!(" {}{}{} ", account_indicator, network_indicator, msg)
|
format!(" {}{}{} ", account_indicator, network_indicator, msg)
|
||||||
@@ -57,7 +52,7 @@ pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
|
|||||||
} else if app.status_message.is_some() {
|
} else if app.status_message.is_some() {
|
||||||
Style::default().fg(Color::Yellow)
|
Style::default().fg(Color::Yellow)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::DarkGray)
|
Style::default().fg(Color::Rgb(160, 160, 160))
|
||||||
};
|
};
|
||||||
|
|
||||||
let footer = Paragraph::new(status).style(style);
|
let footer = Paragraph::new(status).style(style);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 22
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
Инициализация TDLib...
|
[default] Инициализация TDLib...
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 90
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
⏳ Подключение... | Инициализация TDLib...
|
[default] ⏳ Подключение... | Инициализация TDLib...
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 73
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
⏳ Прокси... | Инициализация TDLib...
|
[default] ⏳ Прокси... | Инициализация TDLib...
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 56
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
⚠ Нет сети | Инициализация TDLib...
|
[default] ⚠ Нет сети | Инициализация TDLib...
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 39
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
Инициализация TDLib...
|
[default] Инициализация TDLib...
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/footer.rs
|
source: tests/footer.rs
|
||||||
|
assertion_line: 107
|
||||||
expression: output
|
expression: output
|
||||||
---
|
---
|
||||||
Инициализация TDLib...
|
[default] Инициализация TDLib...
|
||||||
|
|||||||
Reference in New Issue
Block a user