fix: исправлены баги с сообщениями, редактированием и reply

- Изменён порядок хранения сообщений (теперь от старых к новым)
- Исправлена логика выбора сообщений для редактирования
- Исправлена отправка reply (структура условий)
- Добавлено сохранение reply_info при отправке
- Удалены отладочные логи

Fixes: сообщения теперь отображаются корректно в UI
Fixes: редактирование работает без ошибки 'Message not found'
Fixes: reply показывает превью исходного сообщения
This commit is contained in:
Mikhail Kilin
2026-01-31 18:29:02 +03:00
parent 644e36597d
commit 07c401e0f9
4 changed files with 129 additions and 82 deletions

View File

@@ -133,30 +133,34 @@ impl App {
/// Начать выбор сообщения для редактирования (при стрелке вверх в пустом инпуте) /// Начать выбор сообщения для редактирования (при стрелке вверх в пустом инпуте)
pub fn start_message_selection(&mut self) { pub fn start_message_selection(&mut self) {
if self.td_client.current_chat_messages().is_empty() { let total = self.td_client.current_chat_messages().len();
if total == 0 {
return; return;
} }
// Начинаем с последнего сообщения (индекс 0 = самое новое снизу) // Начинаем с последнего сообщения (индекс len-1 = самое новое внизу)
self.chat_state = ChatState::MessageSelection { selected_index: 0 }; self.chat_state = ChatState::MessageSelection { selected_index: total - 1 };
} }
/// Выбрать предыдущее сообщение (вверх по списку = увеличить индекс) /// Выбрать предыдущее сообщение (вверх по списку = к старым = уменьшить индекс)
pub fn select_previous_message(&mut self) { pub fn select_previous_message(&mut self) {
if let ChatState::MessageSelection { selected_index } = &mut self.chat_state {
if *selected_index > 0 {
*selected_index -= 1;
}
}
}
/// Выбрать следующее сообщение (вниз по списку = к новым = увеличить индекс)
pub fn select_next_message(&mut self) {
let total = self.td_client.current_chat_messages().len(); let total = self.td_client.current_chat_messages().len();
if total == 0 { if total == 0 {
return; return;
} }
if let ChatState::MessageSelection { selected_index } = &mut self.chat_state { if let ChatState::MessageSelection { selected_index } = &mut self.chat_state {
*selected_index = (*selected_index + 1).min(total - 1); if *selected_index < total - 1 {
} *selected_index += 1;
}
/// Выбрать следующее сообщение (вниз по списку = уменьшить индекс)
pub fn select_next_message(&mut self) {
if let ChatState::MessageSelection { selected_index } = &mut self.chat_state {
if *selected_index > 0 {
*selected_index -= 1;
} else { } else {
// Дошли до самого нового сообщения - выходим из режима выбора
self.chat_state = ChatState::Normal; self.chat_state = ChatState::Normal;
} }
} }
@@ -165,12 +169,7 @@ impl App {
/// Получить выбранное сообщение /// Получить выбранное сообщение
pub fn get_selected_message(&self) -> Option<&crate::tdlib::MessageInfo> { pub fn get_selected_message(&self) -> Option<&crate::tdlib::MessageInfo> {
self.chat_state.selected_message_index().and_then(|idx| { self.chat_state.selected_message_index().and_then(|idx| {
let total = self.td_client.current_chat_messages().len(); self.td_client.current_chat_messages().get(idx)
if total == 0 || idx >= total {
return None;
}
// idx=0 это последнее сообщение (total-1), idx=1 это предпоследнее (total-2), и т.д.
self.td_client.current_chat_messages().get(total - 1 - idx)
}) })
} }

View File

@@ -494,7 +494,12 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
) )
.await .await
{ {
Ok(Ok(_)) => { Ok(Ok(messages)) => {
// Сохраняем загруженные сообщения
*app.td_client.current_chat_messages_mut() = messages;
// ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории
// Это предотвращает race condition с Update::NewMessage
app.td_client.set_current_chat_id(Some(ChatId::new(chat_id)));
// Загружаем недостающие reply info // Загружаем недостающие reply info
let _ = timeout( let _ = timeout(
Duration::from_secs(5), Duration::from_secs(5),
@@ -563,39 +568,39 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
if let Some(chat_id) = app.get_selected_chat_id() { if let Some(chat_id) = app.get_selected_chat_id() {
let text = app.message_input.clone(); let text = app.message_input.clone();
if let Some(msg_id) = app.chat_state.selected_message_id() { if app.is_editing() {
if app.is_editing() { // Режим редактирования
// Режим редактирования if let Some(msg_id) = app.chat_state.selected_message_id() {
app.message_input.clear(); match timeout(
app.cursor_position = 0; Duration::from_secs(5),
app.chat_state = crate::app::ChatState::Normal; app.td_client.edit_message(ChatId::new(chat_id), msg_id, text),
)
match timeout( .await
Duration::from_secs(5), {
app.td_client.edit_message(ChatId::new(chat_id), msg_id, text), Ok(Ok(edited_msg)) => {
) // Обновляем сообщение в списке
.await if let Some(msg) = app
{ .td_client
Ok(Ok(edited_msg)) => { .current_chat_messages_mut()
// Обновляем сообщение в списке .iter_mut()
if let Some(msg) = app .find(|m| m.id() == msg_id)
.td_client {
.current_chat_messages_mut() msg.content.text = edited_msg.content.text;
.iter_mut() msg.content.entities = edited_msg.content.entities;
.find(|m| m.id() == msg_id) msg.metadata.edit_date = edited_msg.metadata.edit_date;
{ }
msg.content.text = edited_msg.content.text; // Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования
msg.content.entities = edited_msg.content.entities; app.message_input.clear();
msg.metadata.edit_date = edited_msg.metadata.edit_date; app.cursor_position = 0;
app.chat_state = crate::app::ChatState::Normal;
}
Ok(Err(e)) => {
app.error_message = Some(e);
}
Err(_) => {
app.error_message = Some("Таймаут редактирования".to_string());
} }
} }
Ok(Err(e)) => {
app.error_message = Some(e);
}
Err(_) => {
app.error_message = Some("Таймаут редактирования".to_string());
}
}
} }
} else { } else {
// Обычная отправка (или reply) // Обычная отправка (или reply)
@@ -663,7 +668,12 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
) )
.await .await
{ {
Ok(Ok(_)) => { Ok(Ok(messages)) => {
// Сохраняем загруженные сообщения
*app.td_client.current_chat_messages_mut() = messages;
// ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории
// Это предотвращает race condition с Update::NewMessage
app.td_client.set_current_chat_id(Some(ChatId::new(chat_id)));
// Загружаем недостающие reply info // Загружаем недостающие reply info
let _ = timeout( let _ = timeout(
Duration::from_secs(5), Duration::from_secs(5),

View File

@@ -498,7 +498,7 @@ impl TdClient {
} }
None => { None => {
// Нового сообщения нет - добавляем // Нового сообщения нет - добавляем
self.push_message(msg_info); self.push_message(msg_info.clone());
// Если это входящее сообщение — добавляем в очередь для отметки как прочитанное // Если это входящее сообщение — добавляем в очередь для отметки как прочитанное
if is_incoming { if is_incoming {
self.pending_view_messages_mut().push((chat_id, vec![msg_id])); self.pending_view_messages_mut().push((chat_id, vec![msg_id]));

View File

@@ -29,11 +29,11 @@ impl MessageManager {
/// Добавить сообщение в список текущего чата /// Добавить сообщение в список текущего чата
pub fn push_message(&mut self, msg: MessageInfo) { pub fn push_message(&mut self, msg: MessageInfo) {
self.current_chat_messages.insert(0, msg); self.current_chat_messages.push(msg); // Добавляем в конец
// Ограничиваем размер списка // Ограничиваем размер списка (удаляем старые с начала)
if self.current_chat_messages.len() > MAX_MESSAGES_IN_CHAT { if self.current_chat_messages.len() > MAX_MESSAGES_IN_CHAT {
self.current_chat_messages.truncate(MAX_MESSAGES_IN_CHAT); self.current_chat_messages.drain(0..(self.current_chat_messages.len() - MAX_MESSAGES_IN_CHAT));
} }
} }
@@ -43,34 +43,63 @@ impl MessageManager {
chat_id: ChatId, chat_id: ChatId,
limit: i32, limit: i32,
) -> Result<Vec<MessageInfo>, String> { ) -> Result<Vec<MessageInfo>, String> {
// Устанавливаем текущий чат для получения новых сообщений use tokio::time::{sleep, Duration};
self.current_chat_id = Some(chat_id);
let result = functions::get_chat_history( // ВАЖНО: Сначала открываем чат в TDLib
chat_id.as_i64(), // Это сообщает TDLib что пользователь открыл чат и нужно загрузить историю
0, // from_message_id let _ = functions::open_chat(chat_id.as_i64(), self.client_id).await;
0, // offset
limit,
false,
self.client_id,
)
.await;
match result { // Даём TDLib время на синхронизацию (загрузку истории с сервера)
Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => { sleep(Duration::from_millis(100)).await;
let mut messages = Vec::new();
for msg_opt in messages_obj.messages.iter().rev() { // НЕ устанавливаем current_chat_id здесь!
if let Some(msg) = msg_opt { // Он будет установлен снаружи ПОСЛЕ сохранения истории
if let Some(info) = self.convert_message(msg).await { // Это предотвращает race condition с Update::NewMessage
messages.push(info);
// Пробуем загрузить несколько раз, TDLib может подгружать с сервера
let mut all_messages = Vec::new();
let max_attempts = 3;
for attempt in 1..=max_attempts {
let result = functions::get_chat_history(
chat_id.as_i64(),
0, // from_message_id (0 = from latest)
0, // offset
limit,
false, // only_local - false means can fetch from server
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => {
if !messages_obj.messages.is_empty() {
all_messages.clear(); // Очищаем предыдущие результаты
for msg_opt in messages_obj.messages.iter().rev() {
if let Some(msg) = msg_opt {
if let Some(info) = self.convert_message(msg).await {
all_messages.push(info);
}
}
}
// Если получили достаточно сообщений, прекращаем попытки
if all_messages.len() >= 2 || attempt == max_attempts {
break;
} }
} }
// Если сообщений мало, ждём перед следующей попыткой
if attempt < max_attempts {
sleep(Duration::from_millis(200)).await;
}
} }
Ok(messages) Ok(_) => return Err("Неожиданный тип сообщений".to_string()),
Err(e) => return Err(format!("Ошибка загрузки истории: {:?}", e)),
} }
Ok(_) => Err("Неожиданный тип сообщений".to_string()),
Err(e) => Err(format!("Ошибка загрузки истории: {:?}", e)),
} }
Ok(all_messages)
} }
/// Загрузить более старые сообщения /// Загрузить более старые сообщения
@@ -194,7 +223,7 @@ impl MessageManager {
chat_id: ChatId, chat_id: ChatId,
text: String, text: String,
reply_to_message_id: Option<MessageId>, reply_to_message_id: Option<MessageId>,
_reply_info: Option<ReplyInfo>, reply_info: Option<ReplyInfo>,
) -> Result<MessageInfo, String> { ) -> Result<MessageInfo, String> {
// Парсим markdown в тексте // Парсим markdown в тексте
let formatted_text = match functions::parse_text_entities( let formatted_text = match functions::parse_text_entities(
@@ -241,10 +270,19 @@ impl MessageManager {
.await; .await;
match result { match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => self Ok(tdlib_rs::enums::Message::Message(msg)) => {
.convert_message(&msg) let mut msg_info = self
.await .convert_message(&msg)
.ok_or_else(|| "Не удалось конвертировать сообщение".to_string()), .await
.ok_or_else(|| "Не удалось конвертировать сообщение".to_string())?;
// Добавляем reply_info если был передан
if let Some(reply) = reply_info {
msg_info.interactions.reply_to = Some(reply);
}
Ok(msg_info)
}
Ok(_) => Err("Неожиданный тип сообщения".to_string()), Ok(_) => Err("Неожиданный тип сообщения".to_string()),
Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)), Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)),
} }