diff --git a/CONTEXT.md b/CONTEXT.md index ea706f7..e67097d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,6 +1,6 @@ # Текущий контекст проекта -## Статус: Базовая интеграция с TDLib работает +## Статус: Фаза 3 — улучшение UX ### Что сделано @@ -13,27 +13,46 @@ #### Функциональность - Загрузка списка чатов (до 50 штук) +- **Фильтрация чатов**: показываются только чаты из ChatList::Main (без архива) - Отображение названия чата и счётчика непрочитанных - Загрузка истории сообщений при открытии чата - Отображение сообщений с именем отправителя и временем +- **Отправка текстовых сообщений** #### Управление - `j/k` или стрелки — навигация по списку чатов - `д/л` — русская раскладка для j/k -- `Enter` — открыть выбранный чат +- `Enter` — открыть чат / отправить сообщение - `Esc` — закрыть открытый чат - `Ctrl+k` — перейти к первому чату - `Ctrl+R` — обновить список чатов - `Ctrl+C` — выход +- Ввод текста в поле сообщения ### Структура проекта ``` src/ -├── main.rs # Точка входа, UI рендеринг, event loop -├── tdlib/ -│ ├── mod.rs # Модуль экспорта -│ └── client.rs # TdClient: авторизация, загрузка чатов, сообщений +├── main.rs # Точка входа, event loop, TDLib инициализация +├── app/ +│ ├── mod.rs # App структура и состояние +│ └── state.rs # AppScreen enum +├── ui/ +│ ├── mod.rs # Роутинг UI по экранам +│ ├── loading.rs # Экран загрузки +│ ├── auth.rs # Экран авторизации +│ ├── main_screen.rs # Главный экран +│ ├── chat_list.rs # Список чатов +│ ├── messages.rs # Область сообщений +│ └── footer.rs # Подвал с командами +├── input/ +│ ├── mod.rs # Роутинг ввода +│ ├── auth.rs # Обработка ввода на экране авторизации +│ └── main_input.rs # Обработка ввода на главном экране +├── utils.rs # Утилиты (disable_tdlib_logs, format_timestamp) +└── tdlib/ + ├── mod.rs # Модуль экспорта + └── client.rs # TdClient: авторизация, загрузка чатов, сообщений, отправка ``` ### Ключевые решения @@ -44,6 +63,10 @@ src/ 3. **Синхронизация чатов**: Чаты загружаются асинхронно через updates. Main loop периодически синхронизирует `app.chats` с `td_client.chats`. +4. **Фильтрация чатов по ChatList::Main**: Показываем только чаты с позицией в Main списке и ненулевым order. Архивные чаты и связанные группы не отображаются. + +5. **Сортировка по TDLib order**: Используем `position.order` для сортировки чатов (учитывает pinned и время). + ### Зависимости (Cargo.toml) ```toml @@ -65,14 +88,14 @@ API_HASH=your_api_hash ## Что НЕ сделано / TODO -- [ ] Отправка сообщений - [ ] Поиск по чатам - [ ] Папки телеграма (сейчас только "All") - [ ] Отображение онлайн-статуса пользователя - [ ] Markdown форматирование в сообщениях -- [ ] Скролл истории сообщений +- [ ] Скролл истории сообщений (больше 50 сообщений) - [ ] Отметка сообщений как прочитанные - [ ] Обновление чатов в реальном времени (новые сообщения) +- [ ] Загрузка имён пользователей (сейчас показывается User_ID) ## Известные проблемы diff --git a/ROADMAP.md b/ROADMAP.md index 6f3c57c..1dd8db7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -19,7 +19,8 @@ ## Фаза 3: Улучшение UX [IN PROGRESS] -- [ ] Отправка сообщений +- [x] Отправка сообщений +- [x] Фильтрация чатов (только Main, без архива) - [ ] Поиск по чатам (Ctrl+S) - [ ] Скролл истории сообщений - [ ] Загрузка имён пользователей (вместо User_ID) diff --git a/src/tdlib/client.rs b/src/tdlib/client.rs index ca253f5..256a5b3 100644 --- a/src/tdlib/client.rs +++ b/src/tdlib/client.rs @@ -124,14 +124,45 @@ impl TdClient { chat.last_message_date = last_message_date; } - // Пересортируем после обновления - self.chats.sort_by(|a, b| b.last_message_date.cmp(&a.last_message_date)); + // Обновляем позиции если они пришли + for pos in &update.positions { + if matches!(pos.list, ChatList::Main) { + if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) { + chat.order = pos.order; + chat.is_pinned = pos.is_pinned; + } + } + } + + // Пересортируем по order + self.chats.sort_by(|a, b| b.order.cmp(&a.order)); } Update::ChatReadInbox(update) => { if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) { chat.unread_count = update.unread_count; } } + Update::ChatPosition(update) => { + // Обновляем позицию чата или удаляем его из списка + match &update.position.list { + ChatList::Main => { + if update.position.order == 0 { + // Чат больше не в Main (перемещён в архив и т.д.) + self.chats.retain(|c| c.id != update.chat_id); + } else if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) { + // Обновляем позицию существующего чата + chat.order = update.position.order; + chat.is_pinned = update.position.is_pinned; + } + // Пересортируем по order + self.chats.sort_by(|a, b| b.order.cmp(&a.order)); + } + ChatList::Archive | ChatList::Folder(_) => { + // Если чат добавляется в архив или папку, ничего не делаем + // (он уже должен быть удалён из Main) + } + } + } Update::NewMessage(_new_msg) => { // Новые сообщения обрабатываются при обновлении UI } @@ -152,6 +183,24 @@ impl TdClient { } fn add_or_update_chat(&mut self, td_chat: &TdChat) { + // Проверяем, есть ли у чата позиция в ChatList::Main + // Если нет - не добавляем (это архивные чаты или связанные группы) + let main_position = td_chat.positions.iter().find(|pos| { + matches!(pos.list, ChatList::Main) + }); + + // Если чат не в Main списке - удаляем его если был, и выходим + let Some(position) = main_position else { + self.chats.retain(|c| c.id != td_chat.id); + return; + }; + + // Если order == 0, чат не должен отображаться + if position.order == 0 { + self.chats.retain(|c| c.id != td_chat.id); + return; + } + let (last_message, last_message_date) = td_chat .last_message .as_ref() @@ -164,8 +213,8 @@ impl TdClient { last_message, last_message_date, unread_count: td_chat.unread_count, - is_pinned: false, - order: 0, + is_pinned: position.is_pinned, + order: position.order, }; if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) { @@ -173,12 +222,14 @@ impl TdClient { existing.last_message = chat_info.last_message; existing.last_message_date = chat_info.last_message_date; existing.unread_count = chat_info.unread_count; + existing.is_pinned = chat_info.is_pinned; + existing.order = chat_info.order; } else { self.chats.push(chat_info); } - // Сортируем чаты по дате последнего сообщения (новые сверху) - self.chats.sort_by(|a, b| b.last_message_date.cmp(&a.last_message_date)); + // Сортируем чаты по order (TDLib order учитывает pinned и время) + self.chats.sort_by(|a, b| b.order.cmp(&a.order)); } fn convert_message(&self, message: &TdMessage) -> MessageInfo {