style: auto-format entire codebase with cargo fmt (stable rustfmt.toml)
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
This commit is contained in:
@@ -20,7 +20,8 @@ pub async fn handle<T: TdClientTrait>(app: &mut App<T>, key_code: KeyCode) {
|
||||
app.status_message = Some("Отправка номера...".to_string());
|
||||
match with_timeout_msg(
|
||||
Duration::from_secs(10),
|
||||
app.td_client.send_phone_number(app.phone_input().to_string()),
|
||||
app.td_client
|
||||
.send_phone_number(app.phone_input().to_string()),
|
||||
"Таймаут отправки номера",
|
||||
)
|
||||
.await
|
||||
@@ -84,7 +85,8 @@ pub async fn handle<T: TdClientTrait>(app: &mut App<T>, key_code: KeyCode) {
|
||||
app.status_message = Some("Проверка пароля...".to_string());
|
||||
match with_timeout_msg(
|
||||
Duration::from_secs(10),
|
||||
app.td_client.send_password(app.password_input().to_string()),
|
||||
app.td_client
|
||||
.send_password(app.password_input().to_string()),
|
||||
"Таймаут проверки пароля",
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
//! - Editing and sending messages
|
||||
//! - Loading older messages
|
||||
|
||||
use super::chat_list::open_chat_and_load_data;
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods, messages::MessageMethods, modal::ModalMethods,
|
||||
navigation::NavigationMethods,
|
||||
};
|
||||
use crate::app::App;
|
||||
use crate::app::InputMode;
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods, messages::MessageMethods,
|
||||
modal::ModalMethods, navigation::NavigationMethods,
|
||||
};
|
||||
use crate::tdlib::{TdClientTrait, ChatAction};
|
||||
use crate::input::handlers::{copy_to_clipboard, format_message_for_clipboard};
|
||||
use crate::tdlib::{ChatAction, TdClientTrait};
|
||||
use crate::types::{ChatId, MessageId};
|
||||
use crate::utils::{is_non_empty, with_timeout, with_timeout_msg};
|
||||
use crate::input::handlers::{copy_to_clipboard, format_message_for_clipboard};
|
||||
use super::chat_list::open_chat_and_load_data;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Обработка режима выбора сообщения для действий
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Навигацию по сообщениям (Up/Down)
|
||||
/// - Удаление сообщения (d/в/Delete)
|
||||
@@ -29,7 +29,11 @@ use std::time::{Duration, Instant};
|
||||
/// - Пересылку сообщения (f/а)
|
||||
/// - Копирование сообщения (y/н)
|
||||
/// - Добавление реакции (e/у)
|
||||
pub async fn handle_message_selection<T: TdClientTrait>(app: &mut App<T>, _key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_message_selection<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
_key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::MoveUp) => {
|
||||
app.select_previous_message();
|
||||
@@ -44,9 +48,7 @@ pub async fn handle_message_selection<T: TdClientTrait>(app: &mut App<T>, _key:
|
||||
let can_delete =
|
||||
msg.can_be_deleted_only_for_self() || msg.can_be_deleted_for_all_users();
|
||||
if can_delete {
|
||||
app.chat_state = crate::app::ChatState::DeleteConfirmation {
|
||||
message_id: msg.id(),
|
||||
};
|
||||
app.chat_state = crate::app::ChatState::DeleteConfirmation { message_id: msg.id() };
|
||||
}
|
||||
}
|
||||
Some(crate::config::Command::EnterInsertMode) => {
|
||||
@@ -129,17 +131,22 @@ pub async fn handle_message_selection<T: TdClientTrait>(app: &mut App<T>, _key:
|
||||
}
|
||||
|
||||
/// Редактирование существующего сообщения
|
||||
pub async fn edit_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, msg_id: MessageId, text: String) {
|
||||
pub async fn edit_message<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
chat_id: i64,
|
||||
msg_id: MessageId,
|
||||
text: String,
|
||||
) {
|
||||
// Проверяем, что сообщение есть в локальном кэше
|
||||
let msg_exists = app.td_client.current_chat_messages()
|
||||
let msg_exists = app
|
||||
.td_client
|
||||
.current_chat_messages()
|
||||
.iter()
|
||||
.any(|m| m.id() == msg_id);
|
||||
|
||||
if !msg_exists {
|
||||
app.error_message = Some(format!(
|
||||
"Сообщение {} не найдено в кэше чата {}",
|
||||
msg_id.as_i64(), chat_id
|
||||
));
|
||||
app.error_message =
|
||||
Some(format!("Сообщение {} не найдено в кэше чата {}", msg_id.as_i64(), chat_id));
|
||||
app.chat_state = crate::app::ChatState::Normal;
|
||||
app.message_input.clear();
|
||||
app.cursor_position = 0;
|
||||
@@ -148,7 +155,8 @@ pub async fn edit_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, msg_
|
||||
|
||||
match with_timeout_msg(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
|
||||
app.td_client
|
||||
.edit_message(ChatId::new(chat_id), msg_id, text),
|
||||
"Таймаут редактирования",
|
||||
)
|
||||
.await
|
||||
@@ -160,8 +168,12 @@ pub async fn edit_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, msg_
|
||||
let old_reply_to = messages[pos].interactions.reply_to.clone();
|
||||
// Если в старом сообщении был reply и в новом он "Unknown" - сохраняем старый
|
||||
if let Some(old_reply) = old_reply_to {
|
||||
if edited_msg.interactions.reply_to.as_ref()
|
||||
.map_or(true, |r| r.sender_name == "Unknown") {
|
||||
if edited_msg
|
||||
.interactions
|
||||
.reply_to
|
||||
.as_ref()
|
||||
.map_or(true, |r| r.sender_name == "Unknown")
|
||||
{
|
||||
edited_msg.interactions.reply_to = Some(old_reply);
|
||||
}
|
||||
}
|
||||
@@ -189,13 +201,13 @@ pub async fn send_new_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64,
|
||||
};
|
||||
|
||||
// Создаём ReplyInfo ДО отправки, пока сообщение точно доступно
|
||||
let reply_info = app.get_replying_to_message().map(|m| {
|
||||
crate::tdlib::ReplyInfo {
|
||||
let reply_info = app
|
||||
.get_replying_to_message()
|
||||
.map(|m| crate::tdlib::ReplyInfo {
|
||||
message_id: m.id(),
|
||||
sender_name: m.sender_name().to_string(),
|
||||
text: m.text().to_string(),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.message_input.clear();
|
||||
app.cursor_position = 0;
|
||||
@@ -206,11 +218,14 @@ pub async fn send_new_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64,
|
||||
app.last_typing_sent = None;
|
||||
|
||||
// Отменяем typing status
|
||||
app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Cancel).await;
|
||||
app.td_client
|
||||
.send_chat_action(ChatId::new(chat_id), ChatAction::Cancel)
|
||||
.await;
|
||||
|
||||
match with_timeout_msg(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.send_message(ChatId::new(chat_id), text, reply_to_id, reply_info),
|
||||
app.td_client
|
||||
.send_message(ChatId::new(chat_id), text, reply_to_id, reply_info),
|
||||
"Таймаут отправки",
|
||||
)
|
||||
.await
|
||||
@@ -228,7 +243,7 @@ pub async fn send_new_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64,
|
||||
}
|
||||
|
||||
/// Обработка клавиши Enter
|
||||
///
|
||||
///
|
||||
/// Обрабатывает три сценария:
|
||||
/// 1. В режиме выбора сообщения: начать редактирование
|
||||
/// 2. В открытом чате: отправить новое или редактировать существующее сообщение
|
||||
@@ -304,7 +319,8 @@ pub async fn send_reaction<T: TdClientTrait>(app: &mut App<T>) {
|
||||
// Send reaction with timeout
|
||||
let result = with_timeout_msg(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.toggle_reaction(chat_id, message_id, emoji.clone()),
|
||||
app.td_client
|
||||
.toggle_reaction(chat_id, message_id, emoji.clone()),
|
||||
"Таймаут отправки реакции",
|
||||
)
|
||||
.await;
|
||||
@@ -353,7 +369,8 @@ pub async fn load_older_messages_if_needed<T: TdClientTrait>(app: &mut App<T>) {
|
||||
// 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),
|
||||
app.td_client
|
||||
.load_older_messages(ChatId::new(chat_id), oldest_msg_id),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
@@ -368,7 +385,7 @@ pub async fn load_older_messages_if_needed<T: TdClientTrait>(app: &mut App<T>) {
|
||||
}
|
||||
|
||||
/// Обработка ввода клавиатуры в открытом чате
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Backspace/Delete: удаление символов относительно курсора
|
||||
/// - Char: вставка символов в позицию курсора + typing status
|
||||
@@ -408,7 +425,8 @@ pub async fn handle_open_chat_keyboard_input<T: TdClientTrait>(app: &mut App<T>,
|
||||
// Игнорируем символы с Ctrl/Alt модификаторами (кроме Shift)
|
||||
// Это позволяет обрабатывать хоткеи типа Ctrl+U для профиля
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL)
|
||||
|| key.modifiers.contains(KeyModifiers::ALT) {
|
||||
|| key.modifiers.contains(KeyModifiers::ALT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -434,7 +452,9 @@ pub async fn handle_open_chat_keyboard_input<T: TdClientTrait>(app: &mut App<T>,
|
||||
.unwrap_or(true);
|
||||
if should_send_typing {
|
||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
||||
app.td_client.send_chat_action(ChatId::new(chat_id), ChatAction::Typing).await;
|
||||
app.td_client
|
||||
.send_chat_action(ChatId::new(chat_id), ChatAction::Typing)
|
||||
.await;
|
||||
app.last_typing_sent = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
@@ -621,8 +641,7 @@ async fn handle_view_image<T: TdClientTrait>(app: &mut App<T>) {
|
||||
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.clone());
|
||||
photo.download_state = PhotoDownloadState::Downloaded(path.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -640,8 +659,7 @@ async fn handle_view_image<T: TdClientTrait>(app: &mut App<T>) {
|
||||
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::Error(e.clone());
|
||||
photo.download_state = PhotoDownloadState::Error(e.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -660,8 +678,7 @@ async fn handle_view_image<T: TdClientTrait>(app: &mut App<T>) {
|
||||
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.clone());
|
||||
photo.download_state = PhotoDownloadState::Downloaded(path.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -748,13 +765,25 @@ async fn handle_play_voice<T: TdClientTrait>(app: &mut App<T>) {
|
||||
if let Ok(entries) = std::fs::read_dir(parent) {
|
||||
for entry in entries.flatten() {
|
||||
let entry_name = entry.file_name();
|
||||
if entry_name.to_string_lossy().starts_with(&stem.to_string_lossy().to_string()) {
|
||||
if entry_name
|
||||
.to_string_lossy()
|
||||
.starts_with(&stem.to_string_lossy().to_string())
|
||||
{
|
||||
let found_path = entry.path().to_string_lossy().to_string();
|
||||
// Кэшируем найденный файл
|
||||
if let Some(ref mut cache) = app.voice_cache {
|
||||
let _ = cache.store(&file_id.to_string(), Path::new(&found_path));
|
||||
let _ = cache.store(
|
||||
&file_id.to_string(),
|
||||
Path::new(&found_path),
|
||||
);
|
||||
}
|
||||
return handle_play_voice_from_path(app, &found_path, &voice, &msg).await;
|
||||
return handle_play_voice_from_path(
|
||||
app,
|
||||
&found_path,
|
||||
&voice,
|
||||
&msg,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -826,4 +855,3 @@ async fn _download_and_expand<T: TdClientTrait>(app: &mut App<T>, msg_id: crate:
|
||||
// Закомментировано - будет реализовано в Этапе 4
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
//! - Folder selection
|
||||
//! - Opening chats
|
||||
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods, messages::MessageMethods, navigation::NavigationMethods,
|
||||
};
|
||||
use crate::app::App;
|
||||
use crate::app::InputMode;
|
||||
use crate::app::methods::{compose::ComposeMethods, messages::MessageMethods, navigation::NavigationMethods};
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::types::{ChatId, MessageId};
|
||||
use crate::utils::{with_timeout, with_timeout_msg};
|
||||
@@ -15,11 +17,15 @@ use crossterm::event::KeyEvent;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Обработка навигации в списке чатов
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Up/Down/j/k: навигация между чатами
|
||||
/// - Цифры 1-9: переключение папок (1=All, 2-9=папки из TDLib)
|
||||
pub async fn handle_chat_list_navigation<T: TdClientTrait>(app: &mut App<T>, _key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_chat_list_navigation<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
_key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::MoveDown) => {
|
||||
app.next_chat();
|
||||
@@ -65,11 +71,9 @@ pub async fn select_folder<T: TdClientTrait>(app: &mut App<T>, folder_idx: usize
|
||||
let folder_id = folder.id;
|
||||
app.selected_folder_id = Some(folder_id);
|
||||
app.status_message = Some("Загрузка чатов папки...".to_string());
|
||||
let _ = with_timeout(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.load_folder_chats(folder_id, 50),
|
||||
)
|
||||
.await;
|
||||
let _ =
|
||||
with_timeout(Duration::from_secs(5), app.td_client.load_folder_chats(folder_id, 50))
|
||||
.await;
|
||||
app.status_message = None;
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
@@ -114,7 +118,8 @@ pub async fn open_chat_and_load_data<T: TdClientTrait>(app: &mut App<T>, chat_id
|
||||
|
||||
// ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории
|
||||
// Это предотвращает race condition с Update::NewMessage
|
||||
app.td_client.set_current_chat_id(Some(ChatId::new(chat_id)));
|
||||
app.td_client
|
||||
.set_current_chat_id(Some(ChatId::new(chat_id)));
|
||||
|
||||
// Загружаем черновик (локальная операция, мгновенно)
|
||||
app.load_draft();
|
||||
@@ -132,4 +137,4 @@ pub async fn open_chat_and_load_data<T: TdClientTrait>(app: &mut App<T>, chat_id
|
||||
app.status_message = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
//! - Edit mode
|
||||
//! - Cursor movement and text editing
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods, navigation::NavigationMethods, search::SearchMethods,
|
||||
};
|
||||
use crate::app::App;
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::types::ChatId;
|
||||
use crate::utils::with_timeout_msg;
|
||||
@@ -17,12 +17,16 @@ use crossterm::event::KeyEvent;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Обработка режима выбора чата для пересылки сообщения
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Навигацию по списку чатов (Up/Down)
|
||||
/// - Пересылку сообщения в выбранный чат (Enter)
|
||||
/// - Отмену пересылки (Esc)
|
||||
pub async fn handle_forward_mode<T: TdClientTrait>(app: &mut App<T>, _key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_forward_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
_key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::Cancel) => {
|
||||
app.cancel_forward();
|
||||
@@ -63,11 +67,8 @@ pub async fn forward_selected_message<T: TdClientTrait>(app: &mut App<T>) {
|
||||
// Forward the message with timeout
|
||||
let result = with_timeout_msg(
|
||||
Duration::from_secs(5),
|
||||
app.td_client.forward_messages(
|
||||
to_chat_id,
|
||||
ChatId::new(from_chat_id),
|
||||
vec![msg_id],
|
||||
),
|
||||
app.td_client
|
||||
.forward_messages(to_chat_id, ChatId::new(from_chat_id), vec![msg_id]),
|
||||
"Таймаут пересылки",
|
||||
)
|
||||
.await;
|
||||
@@ -81,4 +82,4 @@ pub async fn forward_selected_message<T: TdClientTrait>(app: &mut App<T>) {
|
||||
app.error_message = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
//! - Ctrl+P: View pinned messages
|
||||
//! - Ctrl+F: Search messages in chat
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::methods::{modal::ModalMethods, search::SearchMethods};
|
||||
use crate::app::App;
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::types::ChatId;
|
||||
use crate::utils::{with_timeout, with_timeout_msg};
|
||||
@@ -47,7 +47,8 @@ pub async fn handle_global_commands<T: TdClientTrait>(app: &mut App<T>, key: Key
|
||||
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;
|
||||
let _ =
|
||||
with_timeout(Duration::from_secs(5), app.td_client.load_chats(50)).await;
|
||||
// Синхронизируем muted чаты после обновления
|
||||
app.td_client.sync_notification_muted_chats();
|
||||
app.status_message = None;
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
//! - modal: Modal dialogs (delete confirmation, emoji picker, etc.)
|
||||
//! - search: Search functionality (chat search, message search)
|
||||
|
||||
pub mod clipboard;
|
||||
pub mod global;
|
||||
pub mod profile;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
pub mod clipboard;
|
||||
pub mod compose;
|
||||
pub mod global;
|
||||
pub mod modal;
|
||||
pub mod profile;
|
||||
pub mod search;
|
||||
|
||||
pub use clipboard::*;
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
//! - Pinned messages view
|
||||
//! - Profile information modal
|
||||
|
||||
use crate::app::{AccountSwitcherState, App};
|
||||
use super::scroll_to_message;
|
||||
use crate::app::methods::{modal::ModalMethods, navigation::NavigationMethods};
|
||||
use crate::app::{AccountSwitcherState, App};
|
||||
use crate::input::handlers::get_available_actions_count;
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::types::{ChatId, MessageId};
|
||||
use crate::utils::{with_timeout_msg, modal_handler::handle_yes_no};
|
||||
use crate::input::handlers::get_available_actions_count;
|
||||
use super::scroll_to_message;
|
||||
use crate::utils::{modal_handler::handle_yes_no, with_timeout_msg};
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -65,58 +65,60 @@ pub async fn handle_account_switcher<T: TdClientTrait>(
|
||||
}
|
||||
}
|
||||
}
|
||||
AccountSwitcherState::AddAccount { .. } => {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.account_switcher_back();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
app.account_switcher_confirm_add();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if let Some(AccountSwitcherState::AddAccount {
|
||||
name_input,
|
||||
cursor_position,
|
||||
error,
|
||||
}) = &mut app.account_switcher
|
||||
{
|
||||
if *cursor_position > 0 {
|
||||
let mut chars: Vec<char> = name_input.chars().collect();
|
||||
chars.remove(*cursor_position - 1);
|
||||
*name_input = chars.into_iter().collect();
|
||||
*cursor_position -= 1;
|
||||
*error = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
if let Some(AccountSwitcherState::AddAccount {
|
||||
name_input,
|
||||
cursor_position,
|
||||
error,
|
||||
}) = &mut app.account_switcher
|
||||
{
|
||||
AccountSwitcherState::AddAccount { .. } => match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.account_switcher_back();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
app.account_switcher_confirm_add();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if let Some(AccountSwitcherState::AddAccount {
|
||||
name_input,
|
||||
cursor_position,
|
||||
error,
|
||||
}) = &mut app.account_switcher
|
||||
{
|
||||
if *cursor_position > 0 {
|
||||
let mut chars: Vec<char> = name_input.chars().collect();
|
||||
chars.insert(*cursor_position, c);
|
||||
chars.remove(*cursor_position - 1);
|
||||
*name_input = chars.into_iter().collect();
|
||||
*cursor_position += 1;
|
||||
*cursor_position -= 1;
|
||||
*error = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
if let Some(AccountSwitcherState::AddAccount {
|
||||
name_input,
|
||||
cursor_position,
|
||||
error,
|
||||
}) = &mut app.account_switcher
|
||||
{
|
||||
let mut chars: Vec<char> = name_input.chars().collect();
|
||||
chars.insert(*cursor_position, c);
|
||||
*name_input = chars.into_iter().collect();
|
||||
*cursor_position += 1;
|
||||
*error = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Обработка режима профиля пользователя/чата
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Модалку подтверждения выхода из группы (двухшаговая)
|
||||
/// - Навигацию по действиям профиля (Up/Down)
|
||||
/// - Выполнение выбранного действия (Enter): открыть в браузере, скопировать ID, покинуть группу
|
||||
/// - Выход из режима профиля (Esc)
|
||||
pub async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_profile_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
// Обработка подтверждения выхода из группы
|
||||
let confirmation_step = app.get_leave_group_confirmation_step();
|
||||
if confirmation_step > 0 {
|
||||
@@ -189,10 +191,7 @@ pub async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEve
|
||||
// Действие: Открыть в браузере
|
||||
if let Some(username) = &profile.username {
|
||||
if action_index == current_idx {
|
||||
let url = format!(
|
||||
"https://t.me/{}",
|
||||
username.trim_start_matches('@')
|
||||
);
|
||||
let url = format!("https://t.me/{}", username.trim_start_matches('@'));
|
||||
#[cfg(feature = "url-open")]
|
||||
{
|
||||
match open::that(&url) {
|
||||
@@ -208,7 +207,7 @@ pub async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEve
|
||||
#[cfg(not(feature = "url-open"))]
|
||||
{
|
||||
app.error_message = Some(
|
||||
"Открытие URL недоступно (требуется feature 'url-open')".to_string()
|
||||
"Открытие URL недоступно (требуется feature 'url-open')".to_string(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -233,7 +232,7 @@ pub async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEve
|
||||
}
|
||||
|
||||
/// Обработка Ctrl+U для открытия профиля чата/пользователя
|
||||
///
|
||||
///
|
||||
/// Загружает информацию о профиле и переключает в режим просмотра профиля
|
||||
pub async fn handle_profile_open<T: TdClientTrait>(app: &mut App<T>) {
|
||||
let Some(chat_id) = app.selected_chat_id else {
|
||||
@@ -319,12 +318,16 @@ pub async fn handle_delete_confirmation<T: TdClientTrait>(app: &mut App<T>, key:
|
||||
}
|
||||
|
||||
/// Обработка режима выбора реакции (emoji picker)
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Навигацию по сетке реакций: Left/Right, Up/Down (сетка 8x6)
|
||||
/// - Добавление/удаление реакции (Enter)
|
||||
/// - Выход из режима (Esc)
|
||||
pub async fn handle_reaction_picker_mode<T: TdClientTrait>(app: &mut App<T>, _key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_reaction_picker_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
_key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::MoveLeft) => {
|
||||
app.select_previous_reaction();
|
||||
@@ -335,10 +338,8 @@ pub async fn handle_reaction_picker_mode<T: TdClientTrait>(app: &mut App<T>, _ke
|
||||
app.needs_redraw = true;
|
||||
}
|
||||
Some(crate::config::Command::MoveUp) => {
|
||||
if let crate::app::ChatState::ReactionPicker {
|
||||
selected_index,
|
||||
..
|
||||
} = &mut app.chat_state
|
||||
if let crate::app::ChatState::ReactionPicker { selected_index, .. } =
|
||||
&mut app.chat_state
|
||||
{
|
||||
if *selected_index >= 8 {
|
||||
*selected_index = selected_index.saturating_sub(8);
|
||||
@@ -372,12 +373,16 @@ pub async fn handle_reaction_picker_mode<T: TdClientTrait>(app: &mut App<T>, _ke
|
||||
}
|
||||
|
||||
/// Обработка режима просмотра закреплённых сообщений
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Навигацию по закреплённым сообщениям (Up/Down)
|
||||
/// - Переход к сообщению в истории (Enter)
|
||||
/// - Выход из режима (Esc)
|
||||
pub async fn handle_pinned_mode<T: TdClientTrait>(app: &mut App<T>, _key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_pinned_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
_key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::Cancel) => {
|
||||
app.exit_pinned_mode();
|
||||
@@ -396,4 +401,4 @@ pub async fn handle_pinned_mode<T: TdClientTrait>(app: &mut App<T>, _key: KeyEve
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
//! - Message search mode
|
||||
//! - Search query input
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::methods::{navigation::NavigationMethods, search::SearchMethods};
|
||||
use crate::app::App;
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::types::{ChatId, MessageId};
|
||||
use crate::utils::with_timeout;
|
||||
@@ -17,13 +17,17 @@ use super::chat_list::open_chat_and_load_data;
|
||||
use super::scroll_to_message;
|
||||
|
||||
/// Обработка режима поиска по чатам
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Редактирование поискового запроса (Backspace, Char)
|
||||
/// - Навигацию по отфильтрованному списку (Up/Down)
|
||||
/// - Открытие выбранного чата (Enter)
|
||||
/// - Отмену поиска (Esc)
|
||||
pub async fn handle_chat_search_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_chat_search_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::Cancel) => {
|
||||
app.cancel_search();
|
||||
@@ -40,30 +44,32 @@ pub async fn handle_chat_search_mode<T: TdClientTrait>(app: &mut App<T>, key: Ke
|
||||
Some(crate::config::Command::MoveUp) => {
|
||||
app.previous_filtered_chat();
|
||||
}
|
||||
_ => {
|
||||
match key.code {
|
||||
KeyCode::Backspace => {
|
||||
app.search_query.pop();
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.search_query.push(c);
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
_ => {}
|
||||
_ => match key.code {
|
||||
KeyCode::Backspace => {
|
||||
app.search_query.pop();
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.search_query.push(c);
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Обработка режима поиска по сообщениям в открытом чате
|
||||
///
|
||||
///
|
||||
/// Обрабатывает:
|
||||
/// - Навигацию по результатам поиска (Up/Down/N/n)
|
||||
/// - Переход к выбранному сообщению (Enter)
|
||||
/// - Редактирование поискового запроса (Backspace, Char)
|
||||
/// - Выход из режима поиска (Esc)
|
||||
pub async fn handle_message_search_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent, command: Option<crate::config::Command>) {
|
||||
pub async fn handle_message_search_mode<T: TdClientTrait>(
|
||||
app: &mut App<T>,
|
||||
key: KeyEvent,
|
||||
command: Option<crate::config::Command>,
|
||||
) {
|
||||
match command {
|
||||
Some(crate::config::Command::Cancel) => {
|
||||
app.exit_message_search_mode();
|
||||
@@ -80,33 +86,31 @@ pub async fn handle_message_search_mode<T: TdClientTrait>(app: &mut App<T>, key:
|
||||
app.exit_message_search_mode();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match key.code {
|
||||
KeyCode::Char('N') => {
|
||||
app.select_previous_search_result();
|
||||
}
|
||||
KeyCode::Char('n') => {
|
||||
app.select_next_search_result();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||
return;
|
||||
};
|
||||
query.pop();
|
||||
app.update_search_query(query.clone());
|
||||
perform_message_search(app, &query).await;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||
return;
|
||||
};
|
||||
query.push(c);
|
||||
app.update_search_query(query.clone());
|
||||
perform_message_search(app, &query).await;
|
||||
}
|
||||
_ => {}
|
||||
_ => match key.code {
|
||||
KeyCode::Char('N') => {
|
||||
app.select_previous_search_result();
|
||||
}
|
||||
}
|
||||
KeyCode::Char('n') => {
|
||||
app.select_next_search_result();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||
return;
|
||||
};
|
||||
query.pop();
|
||||
app.update_search_query(query.clone());
|
||||
perform_message_search(app, &query).await;
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||
return;
|
||||
};
|
||||
query.push(c);
|
||||
app.update_search_query(query.clone());
|
||||
perform_message_search(app, &query).await;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,4 +133,4 @@ pub async fn perform_message_search<T: TdClientTrait>(app: &mut App<T>, query: &
|
||||
{
|
||||
app.set_search_results(results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,35 +3,26 @@
|
||||
//! Dispatches keyboard events to specialized handlers based on current app mode.
|
||||
//! Priority order: modals → search → compose → chat → chat list.
|
||||
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods, messages::MessageMethods, modal::ModalMethods,
|
||||
navigation::NavigationMethods, search::SearchMethods,
|
||||
};
|
||||
use crate::app::App;
|
||||
use crate::app::InputMode;
|
||||
use crate::app::methods::{
|
||||
compose::ComposeMethods,
|
||||
messages::MessageMethods,
|
||||
modal::ModalMethods,
|
||||
navigation::NavigationMethods,
|
||||
search::SearchMethods,
|
||||
};
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crate::input::handlers::{
|
||||
chat::{handle_enter_key, handle_message_selection, handle_open_chat_keyboard_input},
|
||||
chat_list::handle_chat_list_navigation,
|
||||
compose::handle_forward_mode,
|
||||
handle_global_commands,
|
||||
modal::{
|
||||
handle_account_switcher,
|
||||
handle_profile_mode, handle_profile_open, handle_delete_confirmation,
|
||||
handle_reaction_picker_mode, handle_pinned_mode,
|
||||
handle_account_switcher, handle_delete_confirmation, handle_pinned_mode,
|
||||
handle_profile_mode, handle_profile_open, handle_reaction_picker_mode,
|
||||
},
|
||||
search::{handle_chat_search_mode, handle_message_search_mode},
|
||||
compose::handle_forward_mode,
|
||||
chat_list::handle_chat_list_navigation,
|
||||
chat::{
|
||||
handle_message_selection, handle_enter_key,
|
||||
handle_open_chat_keyboard_input,
|
||||
},
|
||||
};
|
||||
use crate::tdlib::TdClientTrait;
|
||||
use crossterm::event::KeyEvent;
|
||||
|
||||
|
||||
|
||||
/// Обработка клавиши Esc в Normal mode
|
||||
///
|
||||
/// Закрывает чат с сохранением черновика
|
||||
@@ -55,7 +46,10 @@ async fn handle_escape_normal<T: TdClientTrait>(app: &mut App<T>) {
|
||||
let _ = app.td_client.set_draft_message(chat_id, draft_text).await;
|
||||
} else {
|
||||
// Очищаем черновик если инпут пустой
|
||||
let _ = app.td_client.set_draft_message(chat_id, String::new()).await;
|
||||
let _ = app
|
||||
.td_client
|
||||
.set_draft_message(chat_id, String::new())
|
||||
.await;
|
||||
}
|
||||
|
||||
app.close_chat();
|
||||
@@ -252,7 +246,7 @@ pub async fn handle<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent) {
|
||||
}
|
||||
|
||||
/// Обработка модального окна просмотра изображения
|
||||
///
|
||||
///
|
||||
/// Hotkeys:
|
||||
/// - Esc/q: закрыть модальное окно
|
||||
/// - ←: предыдущее фото в чате
|
||||
@@ -331,4 +325,3 @@ async fn navigate_to_adjacent_photo<T: TdClientTrait>(app: &mut App<T>, directio
|
||||
};
|
||||
app.status_message = Some(msg.to_string());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user