refactor: reduce nesting with early returns and guard clauses
Применены паттерны упрощения вложенности: - handle_profile_mode: упрощён блок Enter с let-else - handle_profile_open: применён early return guard - handle_enter_key: разделена на 3 функции + early returns - edit_message() - редактирование сообщения - send_new_message() - отправка нового сообщения - Сокращено с ~130 до ~40 строк - handle_message_search_mode: извлечена функция perform_message_search() - Упрощены блоки Backspace и Char с let-else Результат: код стал более линейным, уменьшена глубина вложенности с 6+ до 2-3 уровней Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,59 +73,61 @@ async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent)
|
|||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Выполнить выбранное действие
|
// Выполнить выбранное действие
|
||||||
if let Some(profile) = app.get_profile_info() {
|
let Some(profile) = app.get_profile_info() else {
|
||||||
let actions = get_available_actions_count(profile);
|
return;
|
||||||
let action_index = app.get_selected_profile_action().unwrap_or(0);
|
};
|
||||||
|
|
||||||
if action_index < actions {
|
let actions = get_available_actions_count(profile);
|
||||||
// Определяем какое действие выбрано
|
let action_index = app.get_selected_profile_action().unwrap_or(0);
|
||||||
let mut current_idx = 0;
|
|
||||||
|
|
||||||
// Действие: Открыть в браузере
|
// Guard: проверяем, что индекс действия валидный
|
||||||
if profile.username.is_some() {
|
if action_index >= actions {
|
||||||
if action_index == current_idx {
|
return;
|
||||||
if let Some(username) = &profile.username {
|
}
|
||||||
let url = format!(
|
|
||||||
"https://t.me/{}",
|
// Определяем какое действие выбрано
|
||||||
username.trim_start_matches('@')
|
let mut current_idx = 0;
|
||||||
);
|
|
||||||
#[cfg(feature = "url-open")]
|
// Действие: Открыть в браузере
|
||||||
{
|
if let Some(username) = &profile.username {
|
||||||
match open::that(&url) {
|
if action_index == current_idx {
|
||||||
Ok(_) => {
|
let url = format!(
|
||||||
app.status_message = Some(format!("Открыто: {}", url));
|
"https://t.me/{}",
|
||||||
}
|
username.trim_start_matches('@')
|
||||||
Err(e) => {
|
);
|
||||||
app.error_message =
|
#[cfg(feature = "url-open")]
|
||||||
Some(format!("Ошибка открытия браузера: {}", e));
|
{
|
||||||
}
|
match open::that(&url) {
|
||||||
}
|
Ok(_) => {
|
||||||
}
|
app.status_message = Some(format!("Открыто: {}", url));
|
||||||
#[cfg(not(feature = "url-open"))]
|
}
|
||||||
{
|
Err(e) => {
|
||||||
app.error_message = Some(
|
app.error_message =
|
||||||
"Открытие URL недоступно (требуется feature 'url-open')".to_string()
|
Some(format!("Ошибка открытия браузера: {}", e));
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
current_idx += 1;
|
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "url-open"))]
|
||||||
// Действие: Скопировать ID
|
{
|
||||||
if action_index == current_idx {
|
app.error_message = Some(
|
||||||
app.status_message =
|
"Открытие URL недоступно (требуется feature 'url-open')".to_string()
|
||||||
Some(format!("ID скопирован: {}", profile.chat_id));
|
);
|
||||||
return;
|
|
||||||
}
|
|
||||||
current_idx += 1;
|
|
||||||
|
|
||||||
// Действие: Покинуть группу
|
|
||||||
if profile.is_group && action_index == current_idx {
|
|
||||||
app.show_leave_group_confirmation();
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
current_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Действие: Скопировать ID
|
||||||
|
if action_index == current_idx {
|
||||||
|
app.status_message = Some(format!("ID скопирован: {}", profile.chat_id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current_idx += 1;
|
||||||
|
|
||||||
|
// Действие: Покинуть группу
|
||||||
|
if profile.is_group && action_index == current_idx {
|
||||||
|
app.show_leave_group_confirmation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -136,23 +138,25 @@ async fn handle_profile_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent)
|
|||||||
///
|
///
|
||||||
/// Загружает информацию о профиле и переключает в режим просмотра профиля
|
/// Загружает информацию о профиле и переключает в режим просмотра профиля
|
||||||
async fn handle_profile_open<T: TdClientTrait>(app: &mut App<T>) {
|
async fn handle_profile_open<T: TdClientTrait>(app: &mut App<T>) {
|
||||||
if let Some(chat_id) = app.selected_chat_id {
|
let Some(chat_id) = app.selected_chat_id else {
|
||||||
app.status_message = Some("Загрузка профиля...".to_string());
|
return;
|
||||||
match with_timeout_msg(
|
};
|
||||||
Duration::from_secs(5),
|
|
||||||
app.td_client.get_profile_info(chat_id),
|
app.status_message = Some("Загрузка профиля...".to_string());
|
||||||
"Таймаут загрузки профиля",
|
match with_timeout_msg(
|
||||||
)
|
Duration::from_secs(5),
|
||||||
.await
|
app.td_client.get_profile_info(chat_id),
|
||||||
{
|
"Таймаут загрузки профиля",
|
||||||
Ok(profile) => {
|
)
|
||||||
app.enter_profile_mode(profile);
|
.await
|
||||||
app.status_message = None;
|
{
|
||||||
}
|
Ok(profile) => {
|
||||||
Err(e) => {
|
app.enter_profile_mode(profile);
|
||||||
app.error_message = Some(e);
|
app.status_message = None;
|
||||||
app.status_message = None;
|
}
|
||||||
}
|
Err(e) => {
|
||||||
|
app.error_message = Some(e);
|
||||||
|
app.status_message = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +288,105 @@ async fn handle_escape_key<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Редактирование существующего сообщения
|
||||||
|
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()
|
||||||
|
.iter()
|
||||||
|
.any(|m| m.id() == msg_id);
|
||||||
|
|
||||||
|
if !msg_exists {
|
||||||
|
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;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match with_timeout_msg(
|
||||||
|
Duration::from_secs(5),
|
||||||
|
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
|
||||||
|
"Таймаут редактирования",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(mut edited_msg) => {
|
||||||
|
// Сохраняем reply_to из старого сообщения (если есть)
|
||||||
|
let messages = app.td_client.current_chat_messages_mut();
|
||||||
|
if let Some(pos) = messages.iter().position(|m| m.id() == msg_id) {
|
||||||
|
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") {
|
||||||
|
edited_msg.interactions.reply_to = Some(old_reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Заменяем сообщение
|
||||||
|
messages[pos] = edited_msg;
|
||||||
|
}
|
||||||
|
// Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования
|
||||||
|
app.message_input.clear();
|
||||||
|
app.cursor_position = 0;
|
||||||
|
app.chat_state = crate::app::ChatState::Normal;
|
||||||
|
app.needs_redraw = true;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
app.error_message = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Отправка нового сообщения (с опциональным reply)
|
||||||
|
async fn send_new_message<T: TdClientTrait>(app: &mut App<T>, chat_id: i64, text: String) {
|
||||||
|
let reply_to_id = if app.is_replying() {
|
||||||
|
app.chat_state.selected_message_id()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Создаём 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;
|
||||||
|
// Сбрасываем режим reply если он был активен
|
||||||
|
if app.is_replying() {
|
||||||
|
app.chat_state = crate::app::ChatState::Normal;
|
||||||
|
}
|
||||||
|
app.last_typing_sent = None;
|
||||||
|
|
||||||
|
// Отменяем typing status
|
||||||
|
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),
|
||||||
|
"Таймаут отправки",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(sent_msg) => {
|
||||||
|
// Добавляем отправленное сообщение в список (с лимитом)
|
||||||
|
app.td_client.push_message(sent_msg);
|
||||||
|
// Сбрасываем скролл чтобы видеть новое сообщение
|
||||||
|
app.message_scroll_offset = 0;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
app.error_message = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Обработка клавиши Enter
|
/// Обработка клавиши Enter
|
||||||
///
|
///
|
||||||
/// Обрабатывает три сценария:
|
/// Обрабатывает три сценария:
|
||||||
@@ -291,125 +394,8 @@ async fn handle_escape_key<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
/// 2. В открытом чате: отправить новое или редактировать существующее сообщение
|
/// 2. В открытом чате: отправить новое или редактировать существующее сообщение
|
||||||
/// 3. В списке чатов: открыть выбранный чат
|
/// 3. В списке чатов: открыть выбранный чат
|
||||||
async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) {
|
async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) {
|
||||||
if app.selected_chat_id.is_some() {
|
// Сценарий 1: Открытие чата из списка
|
||||||
// Режим выбора сообщения
|
if app.selected_chat_id.is_none() {
|
||||||
if app.is_selecting_message() {
|
|
||||||
// Начать редактирование выбранного сообщения
|
|
||||||
if app.start_editing_selected() {
|
|
||||||
// Редактирование начато
|
|
||||||
} else {
|
|
||||||
// Нельзя редактировать это сообщение
|
|
||||||
app.chat_state = crate::app::ChatState::Normal;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка или редактирование сообщения
|
|
||||||
if is_non_empty(&app.message_input) {
|
|
||||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
|
||||||
let text = app.message_input.clone();
|
|
||||||
|
|
||||||
if app.is_editing() {
|
|
||||||
// Режим редактирования
|
|
||||||
if let Some(msg_id) = app.chat_state.selected_message_id() {
|
|
||||||
// Проверяем, что сообщение есть в локальном кэше
|
|
||||||
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.chat_state = crate::app::ChatState::Normal;
|
|
||||||
app.message_input.clear();
|
|
||||||
app.cursor_position = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match with_timeout_msg(
|
|
||||||
Duration::from_secs(5),
|
|
||||||
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
|
|
||||||
"Таймаут редактирования",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(mut edited_msg) => {
|
|
||||||
// Сохраняем reply_to из старого сообщения (если есть)
|
|
||||||
let messages = app.td_client.current_chat_messages_mut();
|
|
||||||
if let Some(pos) = messages.iter().position(|m| m.id() == msg_id) {
|
|
||||||
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") {
|
|
||||||
edited_msg.interactions.reply_to = Some(old_reply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Заменяем сообщение
|
|
||||||
messages[pos] = edited_msg;
|
|
||||||
}
|
|
||||||
// Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования
|
|
||||||
app.message_input.clear();
|
|
||||||
app.cursor_position = 0;
|
|
||||||
app.chat_state = crate::app::ChatState::Normal;
|
|
||||||
app.needs_redraw = true; // ВАЖНО: перерисовываем UI
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
app.error_message = Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Обычная отправка (или reply)
|
|
||||||
let reply_to_id = if app.is_replying() {
|
|
||||||
app.chat_state.selected_message_id()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
// Создаём 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;
|
|
||||||
// Сбрасываем режим reply если он был активен
|
|
||||||
if app.is_replying() {
|
|
||||||
app.chat_state = crate::app::ChatState::Normal;
|
|
||||||
}
|
|
||||||
app.last_typing_sent = None;
|
|
||||||
|
|
||||||
// Отменяем typing status
|
|
||||||
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),
|
|
||||||
"Таймаут отправки",
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(sent_msg) => {
|
|
||||||
// Добавляем отправленное сообщение в список (с лимитом)
|
|
||||||
app.td_client.push_message(sent_msg);
|
|
||||||
// Сбрасываем скролл чтобы видеть новое сообщение
|
|
||||||
app.message_scroll_offset = 0;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
app.error_message = Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Открываем чат
|
|
||||||
let prev_selected = app.selected_chat_id;
|
let prev_selected = app.selected_chat_id;
|
||||||
app.select_current_chat();
|
app.select_current_chat();
|
||||||
|
|
||||||
@@ -418,6 +404,37 @@ async fn handle_enter_key<T: TdClientTrait>(app: &mut App<T>) {
|
|||||||
open_chat_and_load_data(app, chat_id).await;
|
open_chat_and_load_data(app, chat_id).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сценарий 2: Режим выбора сообщения - начать редактирование
|
||||||
|
if app.is_selecting_message() {
|
||||||
|
if !app.start_editing_selected() {
|
||||||
|
// Нельзя редактировать это сообщение
|
||||||
|
app.chat_state = crate::app::ChatState::Normal;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сценарий 3: Отправка или редактирование сообщения
|
||||||
|
if !is_non_empty(&app.message_input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(chat_id) = app.get_selected_chat_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = app.message_input.clone();
|
||||||
|
|
||||||
|
if app.is_editing() {
|
||||||
|
// Редактирование существующего сообщения
|
||||||
|
if let Some(msg_id) = app.chat_state.selected_message_id() {
|
||||||
|
edit_message(app, chat_id, msg_id, text).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Отправка нового сообщения
|
||||||
|
send_new_message(app, chat_id, text).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,6 +715,27 @@ async fn handle_pinned_mode<T: TdClientTrait>(app: &mut App<T>, key: KeyEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Выполняет поиск по сообщениям с обновлением результатов
|
||||||
|
async fn perform_message_search<T: TdClientTrait>(app: &mut App<T>, query: &str) {
|
||||||
|
let Some(chat_id) = app.get_selected_chat_id() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if query.is_empty() {
|
||||||
|
app.set_search_results(Vec::new());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(results) = with_timeout(
|
||||||
|
Duration::from_secs(3),
|
||||||
|
app.td_client.search_messages(ChatId::new(chat_id), query),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
app.set_search_results(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Обработка режима поиска по сообщениям в открытом чате
|
/// Обработка режима поиска по сообщениям в открытом чате
|
||||||
///
|
///
|
||||||
/// Обрабатывает:
|
/// Обрабатывает:
|
||||||
@@ -735,43 +773,21 @@ async fn handle_message_search_mode<T: TdClientTrait>(app: &mut App<T>, key: Key
|
|||||||
}
|
}
|
||||||
KeyCode::Backspace => {
|
KeyCode::Backspace => {
|
||||||
// Удаляем символ из запроса
|
// Удаляем символ из запроса
|
||||||
if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) {
|
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||||
query.pop();
|
return;
|
||||||
app.update_search_query(query.clone());
|
};
|
||||||
// Выполняем поиск при изменении запроса
|
query.pop();
|
||||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
app.update_search_query(query.clone());
|
||||||
if !query.is_empty() {
|
perform_message_search(app, &query).await;
|
||||||
if let Ok(results) = with_timeout(
|
|
||||||
Duration::from_secs(3),
|
|
||||||
app.td_client.search_messages(ChatId::new(chat_id), &query),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
app.set_search_results(results);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.set_search_results(Vec::new());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
// Добавляем символ к запросу
|
// Добавляем символ к запросу
|
||||||
if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) {
|
let Some(mut query) = app.get_search_query().map(|s| s.to_string()) else {
|
||||||
query.push(c);
|
return;
|
||||||
app.update_search_query(query.clone());
|
};
|
||||||
// Выполняем поиск при изменении запроса
|
query.push(c);
|
||||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
app.update_search_query(query.clone());
|
||||||
if let Ok(results) = with_timeout(
|
perform_message_search(app, &query).await;
|
||||||
Duration::from_secs(3),
|
|
||||||
app.td_client.search_messages(ChatId::new(chat_id), &query),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
app.set_search_results(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user