fixes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
|
|
||||||
# Environment variables (contains API keys)
|
# Environment variables (contains API keys)
|
||||||
.env
|
.env
|
||||||
|
.DS_Store
|
||||||
|
|||||||
12
CONTEXT.md
12
CONTEXT.md
@@ -18,15 +18,16 @@
|
|||||||
- Загрузка истории сообщений при открытии чата
|
- Загрузка истории сообщений при открытии чата
|
||||||
- Отображение сообщений с именем отправителя и временем
|
- Отображение сообщений с именем отправителя и временем
|
||||||
- **Отправка текстовых сообщений**
|
- **Отправка текстовых сообщений**
|
||||||
|
- **Поиск по чатам** (Ctrl+S): фильтрация списка по названию
|
||||||
|
|
||||||
#### Управление
|
#### Управление
|
||||||
- `j/k` или стрелки — навигация по списку чатов
|
- `↑/↓` стрелки — навигация по списку чатов
|
||||||
- `д/л` — русская раскладка для j/k
|
|
||||||
- `Enter` — открыть чат / отправить сообщение
|
- `Enter` — открыть чат / отправить сообщение
|
||||||
- `Esc` — закрыть открытый чат
|
- `Esc` — закрыть открытый чат / отменить поиск
|
||||||
- `Ctrl+k` — перейти к первому чату
|
- `Ctrl+S` — поиск по чатам (фильтрация по названию)
|
||||||
- `Ctrl+R` — обновить список чатов
|
- `Ctrl+R` — обновить список чатов
|
||||||
- `Ctrl+C` — выход
|
- `Ctrl+C` — выход
|
||||||
|
- `Cmd+↑/Cmd+↓` — скролл сообщений в открытом чате
|
||||||
- Ввод текста в поле сообщения
|
- Ввод текста в поле сообщения
|
||||||
|
|
||||||
### Структура проекта
|
### Структура проекта
|
||||||
@@ -63,7 +64,7 @@ src/
|
|||||||
|
|
||||||
3. **Синхронизация чатов**: Чаты загружаются асинхронно через updates. Main loop периодически синхронизирует `app.chats` с `td_client.chats`.
|
3. **Синхронизация чатов**: Чаты загружаются асинхронно через updates. Main loop периодически синхронизирует `app.chats` с `td_client.chats`.
|
||||||
|
|
||||||
4. **Фильтрация чатов по ChatList::Main**: Показываем только чаты с позицией в Main списке и ненулевым order. Архивные чаты и связанные группы не отображаются.
|
4. **Фильтрация чатов**: Все чаты добавляются в список при получении `NewChat` update. Позиции обновляются через `ChatPosition` update.
|
||||||
|
|
||||||
5. **Сортировка по TDLib order**: Используем `position.order` для сортировки чатов (учитывает pinned и время).
|
5. **Сортировка по TDLib order**: Используем `position.order` для сортировки чатов (учитывает pinned и время).
|
||||||
|
|
||||||
@@ -88,7 +89,6 @@ API_HASH=your_api_hash
|
|||||||
|
|
||||||
## Что НЕ сделано / TODO
|
## Что НЕ сделано / TODO
|
||||||
|
|
||||||
- [ ] Поиск по чатам
|
|
||||||
- [ ] Папки телеграма (сейчас только "All")
|
- [ ] Папки телеграма (сейчас только "All")
|
||||||
- [ ] Отображение онлайн-статуса пользователя
|
- [ ] Отображение онлайн-статуса пользователя
|
||||||
- [ ] Markdown форматирование в сообщениях
|
- [ ] Markdown форматирование в сообщениях
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
- [x] Отправка сообщений
|
- [x] Отправка сообщений
|
||||||
- [x] Фильтрация чатов (только Main, без архива)
|
- [x] Фильтрация чатов (только Main, без архива)
|
||||||
- [ ] Поиск по чатам (Ctrl+S)
|
- [x] Поиск по чатам (Ctrl+S)
|
||||||
- [ ] Скролл истории сообщений
|
- [ ] Скролл истории сообщений
|
||||||
- [ ] Загрузка имён пользователей (вместо User_ID)
|
- [ ] Загрузка имён пользователей (вместо User_ID)
|
||||||
- [ ] Отметка сообщений как прочитанные
|
- [ ] Отметка сообщений как прочитанные
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ pub struct App {
|
|||||||
pub folders: Vec<String>,
|
pub folders: Vec<String>,
|
||||||
pub selected_folder: usize,
|
pub selected_folder: usize,
|
||||||
pub is_loading: bool,
|
pub is_loading: bool,
|
||||||
|
// Search state
|
||||||
|
pub is_searching: bool,
|
||||||
|
pub search_query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -49,6 +52,8 @@ impl App {
|
|||||||
folders: vec!["All".to_string()],
|
folders: vec!["All".to_string()],
|
||||||
selected_folder: 0,
|
selected_folder: 0,
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
|
is_searching: false,
|
||||||
|
search_query: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,4 +120,80 @@ impl App {
|
|||||||
self.selected_chat_id
|
self.selected_chat_id
|
||||||
.and_then(|id| self.chats.iter().find(|c| c.id == id))
|
.and_then(|id| self.chats.iter().find(|c| c.id == id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_search(&mut self) {
|
||||||
|
self.is_searching = true;
|
||||||
|
self.search_query.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_search(&mut self) {
|
||||||
|
self.is_searching = false;
|
||||||
|
self.search_query.clear();
|
||||||
|
self.chat_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_filtered_chats(&self) -> Vec<&ChatInfo> {
|
||||||
|
if self.search_query.is_empty() {
|
||||||
|
self.chats.iter().collect()
|
||||||
|
} else {
|
||||||
|
let query = self.search_query.to_lowercase();
|
||||||
|
self.chats
|
||||||
|
.iter()
|
||||||
|
.filter(|c| {
|
||||||
|
// Поиск по названию чата
|
||||||
|
c.title.to_lowercase().contains(&query) ||
|
||||||
|
// Поиск по username (@...)
|
||||||
|
c.username.as_ref()
|
||||||
|
.map(|u| u.to_lowercase().contains(&query))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_filtered_chat(&mut self) {
|
||||||
|
let filtered = self.get_filtered_chats();
|
||||||
|
if filtered.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match self.chat_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= filtered.len() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.chat_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous_filtered_chat(&mut self) {
|
||||||
|
let filtered = self.get_filtered_chats();
|
||||||
|
if filtered.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = match self.chat_list_state.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
filtered.len() - 1
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.chat_list_state.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_filtered_chat(&mut self) {
|
||||||
|
let filtered = self.get_filtered_chats();
|
||||||
|
if let Some(i) = self.chat_list_state.selected() {
|
||||||
|
if let Some(chat) = filtered.get(i) {
|
||||||
|
self.selected_chat_id = Some(chat.id);
|
||||||
|
self.cancel_search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,70 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
app.status_message = None;
|
app.status_message = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('s') if has_ctrl => {
|
||||||
|
// Ctrl+S - начать поиск (только если чат не открыт)
|
||||||
|
if app.selected_chat_id.is_none() {
|
||||||
|
app.start_search();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Режим поиска
|
||||||
|
if app.is_searching {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
app.cancel_search();
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
// Выбрать чат из отфильтрованного списка
|
||||||
|
app.select_filtered_chat();
|
||||||
|
if let Some(chat_id) = app.get_selected_chat_id() {
|
||||||
|
app.status_message = Some("Загрузка сообщений...".to_string());
|
||||||
|
app.message_scroll_offset = 0;
|
||||||
|
match timeout(Duration::from_secs(5), app.td_client.get_chat_history(chat_id, 50)).await {
|
||||||
|
Ok(Ok(messages)) => {
|
||||||
|
app.current_messages = messages;
|
||||||
|
app.status_message = None;
|
||||||
|
}
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
app.error_message = Some(e);
|
||||||
|
app.status_message = None;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
app.error_message = Some("Таймаут загрузки сообщений".to_string());
|
||||||
|
app.status_message = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
app.search_query.pop();
|
||||||
|
// Сбрасываем выделение при изменении запроса
|
||||||
|
app.chat_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
app.next_filtered_chat();
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
app.previous_filtered_chat();
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
app.search_query.push(c);
|
||||||
|
// Сбрасываем выделение при изменении запроса
|
||||||
|
app.chat_list_state.select(Some(0));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Cmd+j/k - навигация (работает и в списке чатов, и для скролла сообщений)
|
// Cmd+j/k - навигация (работает и в списке чатов, и для скролла сообщений)
|
||||||
if has_super {
|
if has_super {
|
||||||
match key.code {
|
match key.code {
|
||||||
// Cmd+j - вниз (следующий чат ИЛИ скролл вниз)
|
// Cmd+Down - вниз (следующий чат ИЛИ скролл вниз)
|
||||||
KeyCode::Char('j') | KeyCode::Char('д') | KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
if app.selected_chat_id.is_some() {
|
if app.selected_chat_id.is_some() {
|
||||||
// В открытом чате - скролл вниз (к новым сообщениям)
|
// В открытом чате - скролл вниз (к новым сообщениям)
|
||||||
if app.message_scroll_offset > 0 {
|
if app.message_scroll_offset > 0 {
|
||||||
@@ -33,8 +89,8 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
app.next_chat();
|
app.next_chat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Cmd+k - вверх (предыдущий чат ИЛИ скролл вверх)
|
// Cmd+Up - вверх (предыдущий чат ИЛИ скролл вверх)
|
||||||
KeyCode::Char('k') | KeyCode::Char('л') | KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
if app.selected_chat_id.is_some() {
|
if app.selected_chat_id.is_some() {
|
||||||
// В открытом чате - скролл вверх (к старым сообщениям)
|
// В открытом чате - скролл вверх (к старым сообщениям)
|
||||||
app.message_scroll_offset += 3;
|
app.message_scroll_offset += 3;
|
||||||
@@ -69,13 +125,6 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+k - в первый чат (только в режиме списка)
|
|
||||||
if has_ctrl && matches!(key.code, KeyCode::Char('k') | KeyCode::Char('л')) {
|
|
||||||
if app.selected_chat_id.is_none() {
|
|
||||||
app.select_first_chat();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enter - открыть чат или отправить сообщение
|
// Enter - открыть чат или отправить сообщение
|
||||||
if key.code == KeyCode::Enter {
|
if key.code == KeyCode::Enter {
|
||||||
@@ -151,14 +200,12 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// В режиме списка чатов - навигация j/k и переключение папок
|
// В режиме списка чатов - навигация стрелками и переключение папок
|
||||||
match key.code {
|
match key.code {
|
||||||
// j или д - следующий чат
|
KeyCode::Down => {
|
||||||
KeyCode::Char('j') | KeyCode::Char('д') | KeyCode::Down => {
|
|
||||||
app.next_chat();
|
app.next_chat();
|
||||||
}
|
}
|
||||||
// k или л - предыдущий чат
|
KeyCode::Up => {
|
||||||
KeyCode::Char('k') | KeyCode::Char('л') | KeyCode::Up => {
|
|
||||||
app.previous_chat();
|
app.previous_chat();
|
||||||
}
|
}
|
||||||
// Цифры - переключение папок
|
// Цифры - переключение папок
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use tdlib_rs::enums::{AuthorizationState, ChatList, MessageContent, Update, User};
|
use std::collections::HashMap;
|
||||||
|
use tdlib_rs::enums::{AuthorizationState, ChatList, ChatType, MessageContent, Update, User};
|
||||||
use tdlib_rs::functions;
|
use tdlib_rs::functions;
|
||||||
use tdlib_rs::types::{Chat as TdChat, Message as TdMessage};
|
use tdlib_rs::types::{Chat as TdChat, Message as TdMessage};
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ pub enum AuthState {
|
|||||||
pub struct ChatInfo {
|
pub struct ChatInfo {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
pub username: Option<String>,
|
||||||
pub last_message: String,
|
pub last_message: String,
|
||||||
pub last_message_date: i32,
|
pub last_message_date: i32,
|
||||||
pub unread_count: i32,
|
pub unread_count: i32,
|
||||||
@@ -44,6 +46,10 @@ pub struct TdClient {
|
|||||||
client_id: i32,
|
client_id: i32,
|
||||||
pub chats: Vec<ChatInfo>,
|
pub chats: Vec<ChatInfo>,
|
||||||
pub current_chat_messages: Vec<MessageInfo>,
|
pub current_chat_messages: Vec<MessageInfo>,
|
||||||
|
/// Кэш usernames: user_id -> username
|
||||||
|
user_usernames: HashMap<i64, String>,
|
||||||
|
/// Связь chat_id -> user_id для приватных чатов
|
||||||
|
chat_user_ids: HashMap<i64, i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -64,6 +70,8 @@ impl TdClient {
|
|||||||
client_id,
|
client_id,
|
||||||
chats: Vec::new(),
|
chats: Vec::new(),
|
||||||
current_chat_messages: Vec::new(),
|
current_chat_messages: Vec::new(),
|
||||||
|
user_usernames: HashMap::new(),
|
||||||
|
chat_user_ids: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +174,23 @@ impl TdClient {
|
|||||||
Update::NewMessage(_new_msg) => {
|
Update::NewMessage(_new_msg) => {
|
||||||
// Новые сообщения обрабатываются при обновлении UI
|
// Новые сообщения обрабатываются при обновлении UI
|
||||||
}
|
}
|
||||||
|
Update::User(update) => {
|
||||||
|
// Сохраняем username пользователя
|
||||||
|
let user = update.user;
|
||||||
|
if let Some(usernames) = user.usernames {
|
||||||
|
if let Some(username) = usernames.active_usernames.first() {
|
||||||
|
self.user_usernames.insert(user.id, username.clone());
|
||||||
|
// Обновляем username в чатах, связанных с этим пользователем
|
||||||
|
for (&chat_id, &user_id) in &self.chat_user_ids.clone() {
|
||||||
|
if user_id == user.id {
|
||||||
|
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) {
|
||||||
|
chat.username = Some(format!("@{}", username));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,23 +208,15 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_or_update_chat(&mut self, td_chat: &TdChat) {
|
fn add_or_update_chat(&mut self, td_chat: &TdChat) {
|
||||||
// Проверяем, есть ли у чата позиция в ChatList::Main
|
// Ищем позицию в Main списке (если есть)
|
||||||
// Если нет - не добавляем (это архивные чаты или связанные группы)
|
|
||||||
let main_position = td_chat.positions.iter().find(|pos| {
|
let main_position = td_chat.positions.iter().find(|pos| {
|
||||||
matches!(pos.list, ChatList::Main)
|
matches!(pos.list, ChatList::Main)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если чат не в Main списке - удаляем его если был, и выходим
|
// Получаем order и is_pinned из позиции, или используем значения по умолчанию
|
||||||
let Some(position) = main_position else {
|
let (order, is_pinned) = main_position
|
||||||
self.chats.retain(|c| c.id != td_chat.id);
|
.map(|p| (p.order, p.is_pinned))
|
||||||
return;
|
.unwrap_or((1, false)); // order=1 чтобы чат отображался
|
||||||
};
|
|
||||||
|
|
||||||
// Если order == 0, чат не должен отображаться
|
|
||||||
if position.order == 0 {
|
|
||||||
self.chats.retain(|c| c.id != td_chat.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (last_message, last_message_date) = td_chat
|
let (last_message, last_message_date) = td_chat
|
||||||
.last_message
|
.last_message
|
||||||
@@ -207,14 +224,25 @@ impl TdClient {
|
|||||||
.map(|m| (extract_message_text_static(m), m.date))
|
.map(|m| (extract_message_text_static(m), m.date))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Извлекаем user_id для приватных чатов и сохраняем связь
|
||||||
|
let username = match &td_chat.r#type {
|
||||||
|
ChatType::Private(private) => {
|
||||||
|
self.chat_user_ids.insert(td_chat.id, private.user_id);
|
||||||
|
// Проверяем, есть ли уже username в кэше
|
||||||
|
self.user_usernames.get(&private.user_id).map(|u| format!("@{}", u))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
let chat_info = ChatInfo {
|
let chat_info = ChatInfo {
|
||||||
id: td_chat.id,
|
id: td_chat.id,
|
||||||
title: td_chat.title.clone(),
|
title: td_chat.title.clone(),
|
||||||
|
username,
|
||||||
last_message,
|
last_message,
|
||||||
last_message_date,
|
last_message_date,
|
||||||
unread_count: td_chat.unread_count,
|
unread_count: td_chat.unread_count,
|
||||||
is_pinned: position.is_pinned,
|
is_pinned,
|
||||||
order: position.order,
|
order,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
|
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
|
||||||
@@ -222,8 +250,15 @@ impl TdClient {
|
|||||||
existing.last_message = chat_info.last_message;
|
existing.last_message = chat_info.last_message;
|
||||||
existing.last_message_date = chat_info.last_message_date;
|
existing.last_message_date = chat_info.last_message_date;
|
||||||
existing.unread_count = chat_info.unread_count;
|
existing.unread_count = chat_info.unread_count;
|
||||||
existing.is_pinned = chat_info.is_pinned;
|
// Обновляем username если он появился
|
||||||
existing.order = chat_info.order;
|
if chat_info.username.is_some() {
|
||||||
|
existing.username = chat_info.username;
|
||||||
|
}
|
||||||
|
// Обновляем позицию только если она пришла
|
||||||
|
if main_position.is_some() {
|
||||||
|
existing.is_pinned = chat_info.is_pinned;
|
||||||
|
existing.order = chat_info.order;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.chats.push(chat_info);
|
self.chats.push(chat_info);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,26 +17,44 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
|||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// Search box
|
// Search box
|
||||||
let search = Paragraph::new("🔍 Search...")
|
let search_text = if app.is_searching {
|
||||||
|
if app.search_query.is_empty() {
|
||||||
|
"🔍 Введите для поиска...".to_string()
|
||||||
|
} else {
|
||||||
|
format!("🔍 {}", app.search_query)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"🔍 Ctrl+S для поиска".to_string()
|
||||||
|
};
|
||||||
|
let search_style = if app.is_searching {
|
||||||
|
Style::default().fg(Color::Yellow)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
};
|
||||||
|
let search = Paragraph::new(search_text)
|
||||||
.block(Block::default().borders(Borders::ALL))
|
.block(Block::default().borders(Borders::ALL))
|
||||||
.style(Style::default().fg(Color::DarkGray));
|
.style(search_style);
|
||||||
f.render_widget(search, chat_chunks[0]);
|
f.render_widget(search, chat_chunks[0]);
|
||||||
|
|
||||||
// Chat list
|
// Chat list (filtered if searching)
|
||||||
let items: Vec<ListItem> = app
|
let filtered_chats = app.get_filtered_chats();
|
||||||
.chats
|
let items: Vec<ListItem> = filtered_chats
|
||||||
.iter()
|
.iter()
|
||||||
.map(|chat| {
|
.map(|chat| {
|
||||||
let is_selected = app.selected_chat_id == Some(chat.id);
|
let is_selected = app.selected_chat_id == Some(chat.id);
|
||||||
let prefix = if is_selected { "▌ " } else { " " };
|
let prefix = if is_selected { "▌ " } else { " " };
|
||||||
|
|
||||||
|
let username_text = chat.username.as_ref()
|
||||||
|
.map(|u| format!(" {}", u))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let unread_badge = if chat.unread_count > 0 {
|
let unread_badge = if chat.unread_count > 0 {
|
||||||
format!(" ({})", chat.unread_count)
|
format!(" ({})", chat.unread_count)
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = format!("{}{}{}", prefix, chat.title, unread_badge);
|
let content = format!("{}{}{}{}", prefix, chat.title, username_text, unread_badge);
|
||||||
let style = Style::default().fg(Color::White);
|
let style = Style::default().fg(Color::White);
|
||||||
|
|
||||||
ListItem::new(content).style(style)
|
ListItem::new(content).style(style)
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
format!(" {} ", msg)
|
format!(" {} ", msg)
|
||||||
} else if let Some(err) = &app.error_message {
|
} else if let Some(err) = &app.error_message {
|
||||||
format!(" Error: {} ", err)
|
format!(" Error: {} ", err)
|
||||||
|
} else if app.is_searching {
|
||||||
|
" j/k: Navigate | Enter: Select | Esc: Cancel ".to_string()
|
||||||
} else if app.selected_chat_id.is_some() {
|
} else if app.selected_chat_id.is_some() {
|
||||||
" Cmd+j/k: Scroll | Enter: Send | Esc: Close | Ctrl+R: Refresh | Ctrl+C: Quit ".to_string()
|
" Cmd+j/k: Scroll | Enter: Send | Esc: Close | Ctrl+R: Refresh | Ctrl+C: Quit ".to_string()
|
||||||
} else {
|
} else {
|
||||||
" Cmd+j/k: Navigate | Ctrl+k: First | Enter: Open | Ctrl+R: Refresh | Ctrl+C: Quit ".to_string()
|
" j/k: Navigate | Enter: Open | Ctrl+S: Search | Ctrl+R: Refresh | Ctrl+C: Quit ".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let style = if app.error_message.is_some() {
|
let style = if app.error_message.is_some() {
|
||||||
|
|||||||
Reference in New Issue
Block a user