diff --git a/src/app/chat_state.rs b/src/app/chat_state.rs index 1f67e54..467d37e 100644 --- a/src/app/chat_state.rs +++ b/src/app/chat_state.rs @@ -14,9 +14,10 @@ pub enum InputMode { } /// Состояния чата - взаимоисключающие режимы работы с чатом -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub enum ChatState { /// Обычный режим - просмотр сообщений, набор текста + #[default] Normal, /// Выбор сообщения для действия (edit/delete/reply/forward/reaction) @@ -90,12 +91,6 @@ pub enum ChatState { }, } -impl Default for ChatState { - fn default() -> Self { - ChatState::Normal - } -} - impl ChatState { /// Проверка: находимся в режиме выбора сообщения pub fn is_message_selection(&self) -> bool { diff --git a/src/config/keybindings.rs b/src/config/keybindings.rs index 58bf00d..1120662 100644 --- a/src/config/keybindings.rs +++ b/src/config/keybindings.rs @@ -110,8 +110,19 @@ pub struct Keybindings { } impl Keybindings { - /// Создаёт дефолтную конфигурацию - pub fn default() -> Self { + /// Ищет команду по клавише + pub fn get_command(&self, event: &KeyEvent) -> Option { + for (command, bindings) in &self.bindings { + if bindings.iter().any(|binding| binding.matches(event)) { + return Some(*command); + } + } + None + } +} + +impl Default for Keybindings { + fn default() -> Self { let mut bindings = HashMap::new(); // Navigation @@ -301,22 +312,6 @@ impl Keybindings { Self { bindings } } - - /// Ищет команду по клавише - pub fn get_command(&self, event: &KeyEvent) -> Option { - for (command, bindings) in &self.bindings { - if bindings.iter().any(|binding| binding.matches(event)) { - return Some(*command); - } - } - None - } -} - -impl Default for Keybindings { - fn default() -> Self { - Self::default() - } } /// Сериализация KeyModifiers @@ -428,8 +423,8 @@ mod key_code_serde { return Ok(KeyCode::Char(c)); } - if s.starts_with("F") { - let n = s[1..].parse().map_err(serde::de::Error::custom)?; + if let Some(suffix) = s.strip_prefix("F") { + let n = suffix.parse().map_err(serde::de::Error::custom)?; return Ok(KeyCode::F(n)); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 7a0adca..abd9015 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -26,7 +26,7 @@ pub use keybindings::{Command, Keybindings}; /// println!("Timezone: {}", config.general.timezone); /// println!("Incoming color: {}", config.colors.incoming_message); /// ``` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Config { /// Общие настройки (timezone и т.д.). #[serde(default)] @@ -260,19 +260,6 @@ impl Default for NotificationsConfig { } } -impl Default for Config { - fn default() -> Self { - Self { - general: GeneralConfig::default(), - colors: ColorsConfig::default(), - keybindings: Keybindings::default(), - notifications: NotificationsConfig::default(), - images: ImagesConfig::default(), - audio: AudioConfig::default(), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/formatting.rs b/src/formatting.rs index 42f6b80..00b29a2 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -126,22 +126,22 @@ pub fn format_text_with_entities( let start = entity.offset as usize; let end = (entity.offset + entity.length) as usize; - for i in start..end.min(chars.len()) { + for item in char_styles.iter_mut().take(end.min(chars.len())).skip(start) { match &entity.r#type { - TextEntityType::Bold => char_styles[i].bold = true, - TextEntityType::Italic => char_styles[i].italic = true, - TextEntityType::Underline => char_styles[i].underline = true, - TextEntityType::Strikethrough => char_styles[i].strikethrough = true, + TextEntityType::Bold => item.bold = true, + TextEntityType::Italic => item.italic = true, + TextEntityType::Underline => item.underline = true, + TextEntityType::Strikethrough => item.strikethrough = true, TextEntityType::Code | TextEntityType::Pre | TextEntityType::PreCode(_) => { - char_styles[i].code = true + item.code = true } - TextEntityType::Spoiler => char_styles[i].spoiler = true, + TextEntityType::Spoiler => item.spoiler = true, TextEntityType::Url | TextEntityType::TextUrl(_) | TextEntityType::EmailAddress - | TextEntityType::PhoneNumber => char_styles[i].url = true, + | TextEntityType::PhoneNumber => item.url = true, TextEntityType::Mention | TextEntityType::MentionName(_) => { - char_styles[i].mention = true + item.mention = true } _ => {} } diff --git a/src/input/handlers/chat.rs b/src/input/handlers/chat.rs index a78c605..5c687c3 100644 --- a/src/input/handlers/chat.rs +++ b/src/input/handlers/chat.rs @@ -172,7 +172,7 @@ pub async fn edit_message( .interactions .reply_to .as_ref() - .map_or(true, |r| r.sender_name == "Unknown") + .is_none_or(|r| r.sender_name == "Unknown") { edited_msg.interactions.reply_to = Some(old_reply); } @@ -780,7 +780,7 @@ async fn handle_play_voice(app: &mut App) { return handle_play_voice_from_path( app, &found_path, - &voice, + voice, &msg, ) .await; @@ -799,7 +799,7 @@ async fn handle_play_voice(app: &mut App) { let _ = cache.store(&file_id.to_string(), Path::new(&audio_path)); } - handle_play_voice_from_path(app, &audio_path, &voice, &msg).await; + handle_play_voice_from_path(app, &audio_path, voice, &msg).await; } VoiceDownloadState::Downloading => { app.status_message = Some("Загрузка голосового...".to_string()); @@ -809,7 +809,7 @@ async fn handle_play_voice(app: &mut App) { let cache_key = file_id.to_string(); if let Some(cached_path) = app.voice_cache.as_mut().and_then(|c| c.get(&cache_key)) { let path_str = cached_path.to_string_lossy().to_string(); - handle_play_voice_from_path(app, &path_str, &voice, &msg).await; + handle_play_voice_from_path(app, &path_str, voice, &msg).await; return; } @@ -822,7 +822,7 @@ async fn handle_play_voice(app: &mut App) { let _ = cache.store(&cache_key, std::path::Path::new(&path)); } - handle_play_voice_from_path(app, &path, &voice, &msg).await; + handle_play_voice_from_path(app, &path, voice, &msg).await; } Err(e) => { app.error_message = Some(format!("Ошибка загрузки: {}", e)); diff --git a/src/message_grouping.rs b/src/message_grouping.rs index f674af1..206ca83 100644 --- a/src/message_grouping.rs +++ b/src/message_grouping.rs @@ -17,7 +17,7 @@ pub enum MessageGroup { sender_name: String, }, /// Сообщение - Message(MessageInfo), + Message(Box), /// Альбом (группа фото с одинаковым media_album_id) Album(Vec), } @@ -78,7 +78,7 @@ pub fn group_messages(messages: &[MessageInfo]) -> Vec { result.push(MessageGroup::Album(std::mem::take(acc))); } else { // Одно сообщение — не альбом - result.push(MessageGroup::Message(acc.remove(0))); + result.push(MessageGroup::Message(Box::new(acc.remove(0)))); } } @@ -137,7 +137,7 @@ pub fn group_messages(messages: &[MessageInfo]) -> Vec { // Обычное сообщение (не альбом) — flush аккумулятор flush_album(&mut album_acc, &mut result); - result.push(MessageGroup::Message(msg.clone())); + result.push(MessageGroup::Message(Box::new(msg.clone()))); } // Flush оставшийся аккумулятор diff --git a/src/tdlib/client.rs b/src/tdlib/client.rs index e34dbd9..8fdd053 100644 --- a/src/tdlib/client.rs +++ b/src/tdlib/client.rs @@ -490,19 +490,6 @@ impl TdClient { // ==================== Helper методы для упрощения обработки updates ==================== - /// Находит мутабельную ссылку на чат по ID. - /// - /// Упрощает повторяющийся паттерн `self.chats_mut().iter_mut().find(...)`. - /// - /// # Arguments - /// - /// * `chat_id` - ID чата для поиска - /// - /// # Returns - /// - /// * `Some(&mut ChatInfo)` - если чат найден - /// * `None` - если чат не найден - /// Обрабатываем одно обновление от TDLib pub fn handle_update(&mut self, update: Update) { match update { diff --git a/src/tdlib/message_converter.rs b/src/tdlib/message_converter.rs index 091be35..5cdf92a 100644 --- a/src/tdlib/message_converter.rs +++ b/src/tdlib/message_converter.rs @@ -116,7 +116,7 @@ pub fn extract_reply_info(client: &TdClient, message: &TdMessage) -> Option { let mut messages = Vec::new(); - for msg_opt in messages_obj.messages.iter().rev() { - if let Some(msg) = msg_opt { - if let Some(info) = self.convert_message(msg).await { - messages.push(info); - } + for msg in messages_obj.messages.iter().rev().flatten() { + if let Some(info) = self.convert_message(msg).await { + messages.push(info); } } Ok(messages) diff --git a/src/tdlib/types.rs b/src/tdlib/types.rs index ab0d955..bab98c4 100644 --- a/src/tdlib/types.rs +++ b/src/tdlib/types.rs @@ -155,6 +155,7 @@ pub struct MessageInfo { impl MessageInfo { /// Создать новое сообщение + #[allow(clippy::too_many_arguments)] pub fn new( id: MessageId, sender_name: String, diff --git a/src/tdlib/update_handlers.rs b/src/tdlib/update_handlers.rs index 379c963..192859a 100644 --- a/src/tdlib/update_handlers.rs +++ b/src/tdlib/update_handlers.rs @@ -105,7 +105,7 @@ pub fn handle_chat_action_update(client: &mut TdClient, update: UpdateChatAction ChatAction::ChoosingSticker => Some("выбирает стикер...".to_string()), ChatAction::RecordingVideoNote => Some("записывает видеосообщение...".to_string()), ChatAction::UploadingVideoNote(_) => Some("отправляет видеосообщение...".to_string()), - ChatAction::Cancel | _ => None, // Отмена или неизвестное действие + _ => None, // Отмена или неизвестное действие }; match action_text { diff --git a/src/ui/components/emoji_picker.rs b/src/ui/components/emoji_picker.rs index d17e2f0..94ac19c 100644 --- a/src/ui/components/emoji_picker.rs +++ b/src/ui/components/emoji_picker.rs @@ -21,7 +21,7 @@ pub fn render_emoji_picker( ) { // Размеры модалки (зависят от количества реакций) let emojis_per_row = 8; - let rows = (available_reactions.len() + emojis_per_row - 1) / emojis_per_row; + let rows = available_reactions.len().div_ceil(emojis_per_row); let modal_width = 50u16; let modal_height = (rows + 4) as u16; // +4 для заголовка, отступов и подсказки diff --git a/src/ui/components/message_bubble.rs b/src/ui/components/message_bubble.rs index d463363..db26cf5 100644 --- a/src/ui/components/message_bubble.rs +++ b/src/ui/components/message_bubble.rs @@ -401,12 +401,10 @@ pub fn render_message_bubble( } else { format!("[{}]", reaction.emoji) } + } else if reaction.count > 1 { + format!("{} {}", reaction.emoji, reaction.count) } else { - if reaction.count > 1 { - format!("{} {}", reaction.emoji, reaction.count) - } else { - reaction.emoji.clone() - } + reaction.emoji.clone() }; let style = if reaction.is_chosen { @@ -548,7 +546,7 @@ pub fn render_album_bubble( let mut deferred: Vec = Vec::new(); let is_selected = messages.iter().any(|m| selected_msg_id == Some(m.id())); - let is_outgoing = messages.first().map_or(false, |m| m.is_outgoing()); + let is_outgoing = messages.first().is_some_and(|m| m.is_outgoing()); // Selection marker let selection_marker = if is_selected { "▶ " } else { "" }; @@ -567,7 +565,7 @@ pub fn render_album_bubble( // Grid layout let cols = photo_count.min(ALBUM_GRID_MAX_COLS); - let rows = (photo_count + cols - 1) / cols; + let rows = photo_count.div_ceil(cols); // Добавляем маркер выбора на первую строку if is_selected { diff --git a/src/ui/messages.rs b/src/ui/messages.rs index b931e8e..c48fda5 100644 --- a/src/ui/messages.rs +++ b/src/ui/messages.rs @@ -309,11 +309,7 @@ fn render_message_list(f: &mut Frame, area: Rect, app: &mut Ap let total_lines = lines.len(); // Базовый скролл (показываем последние сообщения) - let base_scroll = if total_lines > visible_height { - total_lines - visible_height - } else { - 0 - }; + let base_scroll = total_lines.saturating_sub(visible_height); // Если выбрано сообщение, автоскроллим к нему let scroll_offset = if app.is_selecting_message() { @@ -431,7 +427,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) { 1 }; // Минимум 3 строки (1 контент + 2 рамки), максимум 10 - let input_height = (input_lines + 2).min(10).max(3); + let input_height = (input_lines + 2).clamp(3, 10); // Проверяем, есть ли закреплённое сообщение let has_pinned = app.td_client.current_pinned_message().is_some();