Implement complete trait-based dependency injection pattern for TdClient to enable testing with FakeTdClient mock. Fix critical stack overflow bugs caused by infinite recursion in trait implementations. Breaking Changes: - App is now generic: App<T: TdClientTrait = TdClient> - All UI and input handlers are generic over TdClientTrait - TdClient methods now accessed through trait interface New Files: - src/tdlib/trait.rs: TdClientTrait definition with 40+ methods - src/tdlib/client_impl.rs: TdClientTrait impl for TdClient - tests/helpers/fake_tdclient_impl.rs: TdClientTrait impl for FakeTdClient Critical Fixes: - Fix stack overflow in send_message, edit_message, delete_messages - Fix stack overflow in forward_messages, current_chat_messages - Fix stack overflow in current_pinned_message - All methods now call message_manager directly to avoid recursion Testing: - FakeTdClient supports configurable auth_state for auth screen tests - Added pinned message support in FakeTdClient - All 196+ tests passing (188 tests + 8 benchmarks) Dependencies: - Added async-trait = "0.1" Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
87 lines
3.3 KiB
Rust
87 lines
3.3 KiB
Rust
//! Global commands that work from any screen
|
|
//!
|
|
//! Handles Ctrl+ combinations:
|
|
//! - Ctrl+R: Refresh chats
|
|
//! - Ctrl+S: Start search
|
|
//! - Ctrl+P: View pinned messages
|
|
//! - Ctrl+F: Search messages in chat
|
|
|
|
use crate::app::App;
|
|
use crate::tdlib::TdClientTrait;
|
|
use crate::types::ChatId;
|
|
use crate::utils::{with_timeout, with_timeout_msg};
|
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
use std::time::Duration;
|
|
|
|
/// Обрабатывает глобальные команды (Ctrl+ combinations).
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` если команда была обработана, `false` если нет
|
|
pub async fn handle_global_commands<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent) -> bool {
|
|
let has_ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
|
|
|
match key.code {
|
|
KeyCode::Char('r') if has_ctrl => {
|
|
// Ctrl+R - обновить список чатов
|
|
app.status_message = Some("Обновление чатов...".to_string());
|
|
let _ = with_timeout(Duration::from_secs(5), app.td_client.load_chats(50)).await;
|
|
app.status_message = None;
|
|
true
|
|
}
|
|
KeyCode::Char('s') if has_ctrl => {
|
|
// Ctrl+S - начать поиск (только если чат не открыт)
|
|
if app.selected_chat_id.is_none() {
|
|
app.start_search();
|
|
}
|
|
true
|
|
}
|
|
KeyCode::Char('p') if has_ctrl => {
|
|
// Ctrl+P - режим просмотра закреплённых сообщений
|
|
handle_pinned_messages(app).await;
|
|
true
|
|
}
|
|
KeyCode::Char('f') if has_ctrl => {
|
|
// Ctrl+F - поиск по сообщениям в открытом чате
|
|
if app.selected_chat_id.is_some()
|
|
&& !app.is_pinned_mode()
|
|
&& !app.is_message_search_mode()
|
|
{
|
|
app.enter_message_search_mode();
|
|
}
|
|
true
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Обрабатывает загрузку и отображение закреплённых сообщений
|
|
async fn handle_pinned_messages<T: TdClientTrait>(app: &mut App<T>) {
|
|
if app.selected_chat_id.is_some() && !app.is_pinned_mode() {
|
|
if let Some(chat_id) = app.get_selected_chat_id() {
|
|
app.status_message = Some("Загрузка закреплённых...".to_string());
|
|
match with_timeout_msg(
|
|
Duration::from_secs(5),
|
|
app.td_client.get_pinned_messages(ChatId::new(chat_id)),
|
|
"Таймаут загрузки",
|
|
)
|
|
.await
|
|
{
|
|
Ok(messages) => {
|
|
let messages: Vec<crate::tdlib::MessageInfo> = messages;
|
|
if messages.is_empty() {
|
|
app.status_message = Some("Нет закреплённых сообщений".to_string());
|
|
} else {
|
|
app.enter_pinned_mode(messages);
|
|
app.status_message = None;
|
|
}
|
|
}
|
|
Err(e) => {
|
|
app.error_message = Some(e);
|
|
app.status_message = None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|