fix: исправлены баги с сообщениями, редактированием и reply
- Изменён порядок хранения сообщений (теперь от старых к новым) - Исправлена логика выбора сообщений для редактирования - Исправлена отправка reply (структура условий) - Добавлено сохранение reply_info при отправке - Удалены отладочные логи Fixes: сообщения теперь отображаются корректно в UI Fixes: редактирование работает без ошибки 'Message not found' Fixes: reply показывает превью исходного сообщения
This commit is contained in:
@@ -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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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]));
|
||||||
|
|||||||
@@ -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)),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user