commit
This commit is contained in:
@@ -22,8 +22,8 @@ pub struct App {
|
||||
pub current_messages: Vec<MessageInfo>,
|
||||
pub message_input: String,
|
||||
pub message_scroll_offset: usize,
|
||||
pub folders: Vec<String>,
|
||||
pub selected_folder: usize,
|
||||
/// None = All (основной список), Some(id) = папка с id
|
||||
pub selected_folder_id: Option<i32>,
|
||||
pub is_loading: bool,
|
||||
// Search state
|
||||
pub is_searching: bool,
|
||||
@@ -49,8 +49,7 @@ impl App {
|
||||
current_messages: Vec::new(),
|
||||
message_input: String::new(),
|
||||
message_scroll_offset: 0,
|
||||
folders: vec!["All".to_string()],
|
||||
selected_folder: 0,
|
||||
selected_folder_id: None, // None = All
|
||||
is_loading: true,
|
||||
is_searching: false,
|
||||
search_query: String::new(),
|
||||
@@ -58,12 +57,13 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn next_chat(&mut self) {
|
||||
if self.chats.is_empty() {
|
||||
let filtered = self.get_filtered_chats();
|
||||
if filtered.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = match self.chat_list_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.chats.len() - 1 {
|
||||
if i >= filtered.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
@@ -75,13 +75,14 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn previous_chat(&mut self) {
|
||||
if self.chats.is_empty() {
|
||||
let filtered = self.get_filtered_chats();
|
||||
if filtered.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = match self.chat_list_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.chats.len() - 1
|
||||
filtered.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
@@ -92,8 +93,9 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn select_current_chat(&mut self) {
|
||||
let filtered = self.get_filtered_chats();
|
||||
if let Some(i) = self.chat_list_state.selected() {
|
||||
if let Some(chat) = self.chats.get(i) {
|
||||
if let Some(chat) = filtered.get(i) {
|
||||
self.selected_chat_id = Some(chat.id);
|
||||
}
|
||||
}
|
||||
@@ -134,12 +136,20 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn get_filtered_chats(&self) -> Vec<&ChatInfo> {
|
||||
let folder_filtered: Vec<&ChatInfo> = match self.selected_folder_id {
|
||||
None => self.chats.iter().collect(), // All - показываем все
|
||||
Some(folder_id) => self.chats
|
||||
.iter()
|
||||
.filter(|c| c.folder_ids.contains(&folder_id))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
if self.search_query.is_empty() {
|
||||
self.chats.iter().collect()
|
||||
folder_filtered
|
||||
} else {
|
||||
let query = self.search_query.to_lowercase();
|
||||
self.chats
|
||||
.iter()
|
||||
folder_filtered
|
||||
.into_iter()
|
||||
.filter(|c| {
|
||||
// Поиск по названию чата
|
||||
c.title.to_lowercase().contains(&query) ||
|
||||
|
||||
@@ -4,7 +4,6 @@ use tokio::time::timeout;
|
||||
use crate::app::App;
|
||||
|
||||
pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
let has_super = key.modifiers.contains(KeyModifiers::SUPER);
|
||||
let has_ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||
|
||||
// Глобальные команды (работают всегда)
|
||||
@@ -74,56 +73,6 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cmd+j/k - навигация (работает и в списке чатов, и для скролла сообщений)
|
||||
if has_super {
|
||||
match key.code {
|
||||
// Cmd+Down - вниз (следующий чат ИЛИ скролл вниз)
|
||||
KeyCode::Down => {
|
||||
if app.selected_chat_id.is_some() {
|
||||
// В открытом чате - скролл вниз (к новым сообщениям)
|
||||
if app.message_scroll_offset > 0 {
|
||||
app.message_scroll_offset = app.message_scroll_offset.saturating_sub(3);
|
||||
}
|
||||
} else {
|
||||
// В списке чатов - следующий чат
|
||||
app.next_chat();
|
||||
}
|
||||
}
|
||||
// Cmd+Up - вверх (предыдущий чат ИЛИ скролл вверх)
|
||||
KeyCode::Up => {
|
||||
if app.selected_chat_id.is_some() {
|
||||
// В открытом чате - скролл вверх (к старым сообщениям)
|
||||
app.message_scroll_offset += 3;
|
||||
|
||||
// Проверяем, нужно ли подгрузить старые сообщения
|
||||
if !app.current_messages.is_empty() {
|
||||
let oldest_msg_id = app.current_messages.first().map(|m| m.id).unwrap_or(0);
|
||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
||||
// Подгружаем больше сообщений если скролл близко к верху
|
||||
if app.message_scroll_offset > app.current_messages.len().saturating_sub(10) {
|
||||
if let Ok(Ok(older)) = timeout(
|
||||
Duration::from_secs(3),
|
||||
app.td_client.load_older_messages(chat_id, oldest_msg_id, 20)
|
||||
).await {
|
||||
if !older.is_empty() {
|
||||
// Добавляем старые сообщения в начало
|
||||
let mut new_messages = older;
|
||||
new_messages.extend(app.current_messages.drain(..));
|
||||
app.current_messages = new_messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// В списке чатов - предыдущий чат
|
||||
app.previous_chat();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Enter - открыть чат или отправить сообщение
|
||||
@@ -188,7 +137,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ввод текста в режиме открытого чата
|
||||
// Режим открытого чата
|
||||
if app.selected_chat_id.is_some() {
|
||||
match key.code {
|
||||
KeyCode::Backspace => {
|
||||
@@ -197,6 +146,38 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
KeyCode::Char(c) => {
|
||||
app.message_input.push(c);
|
||||
}
|
||||
// Стрелки - скролл сообщений
|
||||
KeyCode::Down => {
|
||||
// Скролл вниз (к новым сообщениям)
|
||||
if app.message_scroll_offset > 0 {
|
||||
app.message_scroll_offset = app.message_scroll_offset.saturating_sub(3);
|
||||
}
|
||||
}
|
||||
KeyCode::Up => {
|
||||
// Скролл вверх (к старым сообщениям)
|
||||
app.message_scroll_offset += 3;
|
||||
|
||||
// Проверяем, нужно ли подгрузить старые сообщения
|
||||
if !app.current_messages.is_empty() {
|
||||
let oldest_msg_id = app.current_messages.first().map(|m| m.id).unwrap_or(0);
|
||||
if let Some(chat_id) = app.get_selected_chat_id() {
|
||||
// Подгружаем больше сообщений если скролл близко к верху
|
||||
if app.message_scroll_offset > app.current_messages.len().saturating_sub(10) {
|
||||
if let Ok(Ok(older)) = timeout(
|
||||
Duration::from_secs(3),
|
||||
app.td_client.load_older_messages(chat_id, oldest_msg_id, 20)
|
||||
).await {
|
||||
if !older.is_empty() {
|
||||
// Добавляем старые сообщения в начало
|
||||
let mut new_messages = older;
|
||||
new_messages.extend(app.current_messages.drain(..));
|
||||
app.current_messages = new_messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
@@ -208,12 +189,24 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
||||
KeyCode::Up => {
|
||||
app.previous_chat();
|
||||
}
|
||||
// Цифры - переключение папок
|
||||
// Цифры 1-9 - переключение папок
|
||||
KeyCode::Char(c) if c >= '1' && c <= '9' => {
|
||||
let folder_idx = (c as usize) - ('1' as usize);
|
||||
if folder_idx < app.folders.len() {
|
||||
app.selected_folder = folder_idx;
|
||||
let folder_num = (c as usize) - ('1' as usize); // 0-based
|
||||
if folder_num == 0 {
|
||||
// 1 = All
|
||||
app.selected_folder_id = None;
|
||||
} else {
|
||||
// 2, 3, 4... = папки из TDLib
|
||||
if let Some(folder) = app.td_client.folders.get(folder_num - 1) {
|
||||
let folder_id = folder.id;
|
||||
app.selected_folder_id = Some(folder_id);
|
||||
// Загружаем чаты папки
|
||||
app.status_message = Some("Загрузка чатов папки...".to_string());
|
||||
let _ = timeout(Duration::from_secs(5), app.td_client.load_folder_chats(folder_id, 50)).await;
|
||||
app.status_message = None;
|
||||
}
|
||||
}
|
||||
app.chat_list_state.select(Some(0));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
use tdlib_rs::enums::{AuthorizationState, ChatList, ChatType, MessageContent, Update, User};
|
||||
use tdlib_rs::enums::{AuthorizationState, ChatList, ChatType, MessageContent, Update, User, UserStatus};
|
||||
use tdlib_rs::functions;
|
||||
use tdlib_rs::types::{Chat as TdChat, Message as TdMessage};
|
||||
|
||||
@@ -29,6 +29,8 @@ pub struct ChatInfo {
|
||||
pub order: i64,
|
||||
/// ID последнего прочитанного исходящего сообщения (для галочек)
|
||||
pub last_read_outbox_message_id: i64,
|
||||
/// ID папок, в которых находится чат
|
||||
pub folder_ids: Vec<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -41,6 +43,29 @@ pub struct MessageInfo {
|
||||
pub is_read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FolderInfo {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Онлайн-статус пользователя
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UserOnlineStatus {
|
||||
/// Онлайн
|
||||
Online,
|
||||
/// Был недавно (менее часа назад)
|
||||
Recently,
|
||||
/// Был на этой неделе
|
||||
LastWeek,
|
||||
/// Был в этом месяце
|
||||
LastMonth,
|
||||
/// Давно не был
|
||||
LongTimeAgo,
|
||||
/// Оффлайн с указанием времени (unix timestamp)
|
||||
Offline(i32),
|
||||
}
|
||||
|
||||
pub struct TdClient {
|
||||
pub auth_state: AuthState,
|
||||
pub api_id: i32,
|
||||
@@ -60,6 +85,12 @@ pub struct TdClient {
|
||||
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
|
||||
/// Очередь user_id для загрузки имён
|
||||
pub pending_user_ids: Vec<i64>,
|
||||
/// Папки чатов
|
||||
pub folders: Vec<FolderInfo>,
|
||||
/// Позиция основного списка среди папок
|
||||
pub main_chat_list_position: i32,
|
||||
/// Онлайн-статусы пользователей: user_id -> status
|
||||
user_statuses: HashMap<i64, UserOnlineStatus>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -86,6 +117,9 @@ impl TdClient {
|
||||
chat_user_ids: HashMap::new(),
|
||||
pending_view_messages: Vec::new(),
|
||||
pending_user_ids: Vec::new(),
|
||||
folders: Vec::new(),
|
||||
main_chat_list_position: 0,
|
||||
user_statuses: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +131,13 @@ impl TdClient {
|
||||
self.client_id
|
||||
}
|
||||
|
||||
/// Получение онлайн-статуса пользователя по chat_id (для приватных чатов)
|
||||
pub fn get_user_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> {
|
||||
self.chat_user_ids
|
||||
.get(&chat_id)
|
||||
.and_then(|user_id| self.user_statuses.get(user_id))
|
||||
}
|
||||
|
||||
/// Инициализация TDLib с параметрами
|
||||
pub async fn init(&mut self) -> Result<(), String> {
|
||||
let result = functions::set_tdlib_parameters(
|
||||
@@ -193,9 +234,22 @@ impl TdClient {
|
||||
// Пересортируем по order
|
||||
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
||||
}
|
||||
ChatList::Archive | ChatList::Folder(_) => {
|
||||
// Если чат добавляется в архив или папку, ничего не делаем
|
||||
// (он уже должен быть удалён из Main)
|
||||
ChatList::Folder(folder) => {
|
||||
// Обновляем folder_ids для чата
|
||||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if update.position.order == 0 {
|
||||
// Чат удалён из папки
|
||||
chat.folder_ids.retain(|&id| id != folder.chat_folder_id);
|
||||
} else {
|
||||
// Чат добавлен в папку
|
||||
if !chat.folder_ids.contains(&folder.chat_folder_id) {
|
||||
chat.folder_ids.push(folder.chat_folder_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatList::Archive => {
|
||||
// Архив пока не обрабатываем
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,6 +274,16 @@ impl TdClient {
|
||||
// Сохраняем имя и username пользователя
|
||||
let user = update.user;
|
||||
|
||||
// Пропускаем удалённые аккаунты (пустое имя)
|
||||
if user.first_name.is_empty() && user.last_name.is_empty() {
|
||||
// Удаляем чаты с этим пользователем из списка
|
||||
let user_id = user.id;
|
||||
self.chats.retain(|c| {
|
||||
self.chat_user_ids.get(&c.id) != Some(&user_id)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Сохраняем display name (first_name + last_name)
|
||||
let display_name = if user.last_name.is_empty() {
|
||||
user.first_name.clone()
|
||||
@@ -243,6 +307,30 @@ impl TdClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
Update::ChatFolders(update) => {
|
||||
// Обновляем список папок
|
||||
self.folders = update
|
||||
.chat_folders
|
||||
.into_iter()
|
||||
.map(|f| FolderInfo {
|
||||
id: f.id,
|
||||
name: f.title,
|
||||
})
|
||||
.collect();
|
||||
self.main_chat_list_position = update.main_chat_list_position;
|
||||
}
|
||||
Update::UserStatus(update) => {
|
||||
// Обновляем онлайн-статус пользователя
|
||||
let status = match update.status {
|
||||
UserStatus::Online(_) => UserOnlineStatus::Online,
|
||||
UserStatus::Offline(offline) => UserOnlineStatus::Offline(offline.was_online),
|
||||
UserStatus::Recently(_) => UserOnlineStatus::Recently,
|
||||
UserStatus::LastWeek(_) => UserOnlineStatus::LastWeek,
|
||||
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
|
||||
UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
|
||||
};
|
||||
self.user_statuses.insert(update.user_id, status);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -260,6 +348,13 @@ impl TdClient {
|
||||
}
|
||||
|
||||
fn add_or_update_chat(&mut self, td_chat: &TdChat) {
|
||||
// Пропускаем удалённые аккаунты
|
||||
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
|
||||
// Удаляем из списка если уже был добавлен
|
||||
self.chats.retain(|c| c.id != td_chat.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем позицию в Main списке (если есть)
|
||||
let main_position = td_chat.positions.iter().find(|pos| {
|
||||
matches!(pos.list, ChatList::Main)
|
||||
@@ -286,6 +381,19 @@ impl TdClient {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Извлекаем ID папок из позиций
|
||||
let folder_ids: Vec<i32> = td_chat
|
||||
.positions
|
||||
.iter()
|
||||
.filter_map(|pos| {
|
||||
if let ChatList::Folder(folder) = &pos.list {
|
||||
Some(folder.chat_folder_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let chat_info = ChatInfo {
|
||||
id: td_chat.id,
|
||||
title: td_chat.title.clone(),
|
||||
@@ -296,6 +404,7 @@ impl TdClient {
|
||||
is_pinned,
|
||||
order,
|
||||
last_read_outbox_message_id: td_chat.last_read_outbox_message_id,
|
||||
folder_ids,
|
||||
};
|
||||
|
||||
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
|
||||
@@ -304,6 +413,7 @@ impl TdClient {
|
||||
existing.last_message_date = chat_info.last_message_date;
|
||||
existing.unread_count = chat_info.unread_count;
|
||||
existing.last_read_outbox_message_id = chat_info.last_read_outbox_message_id;
|
||||
existing.folder_ids = chat_info.folder_ids;
|
||||
// Обновляем username если он появился
|
||||
if chat_info.username.is_some() {
|
||||
existing.username = chat_info.username;
|
||||
@@ -417,6 +527,25 @@ impl TdClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Загрузка чатов для конкретной папки
|
||||
pub async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String> {
|
||||
let chat_list = ChatList::Folder(tdlib_rs::types::ChatListFolder {
|
||||
chat_folder_id: folder_id,
|
||||
});
|
||||
|
||||
let result = functions::load_chats(
|
||||
Some(chat_list),
|
||||
limit,
|
||||
self.client_id,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("Ошибка загрузки чатов папки: {:?}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Загрузка истории сообщений чата
|
||||
pub async fn get_chat_history(
|
||||
&mut self,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod client;
|
||||
|
||||
pub use client::TdClient;
|
||||
pub use client::UserOnlineStatus;
|
||||
|
||||
@@ -5,6 +5,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
use crate::app::App;
|
||||
use crate::tdlib::UserOnlineStatus;
|
||||
|
||||
pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
||||
let chat_chunks = Layout::default()
|
||||
@@ -43,7 +44,14 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
||||
.map(|chat| {
|
||||
let is_selected = app.selected_chat_id == Some(chat.id);
|
||||
let pin_icon = if chat.is_pinned { "📌 " } else { "" };
|
||||
let prefix = if is_selected { "▌ " } else { " " };
|
||||
|
||||
// Онлайн-статус (зелёная точка для онлайн)
|
||||
let status_icon = match app.td_client.get_user_status_by_chat_id(chat.id) {
|
||||
Some(UserOnlineStatus::Online) => "● ",
|
||||
_ => " ",
|
||||
};
|
||||
|
||||
let prefix = if is_selected { "▌" } else { " " };
|
||||
|
||||
let username_text = chat.username.as_ref()
|
||||
.map(|u| format!(" {}", u))
|
||||
@@ -55,8 +63,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let content = format!("{}{}{}{}{}", prefix, pin_icon, chat.title, username_text, unread_badge);
|
||||
let style = Style::default().fg(Color::White);
|
||||
let content = format!("{}{}{}{}{}{}", prefix, status_icon, pin_icon, chat.title, username_text, unread_badge);
|
||||
|
||||
// Цвет зависит от онлайн-статуса
|
||||
let style = match app.td_client.get_user_status_by_chat_id(chat.id) {
|
||||
Some(UserOnlineStatus::Online) => Style::default().fg(Color::Green),
|
||||
_ => Style::default().fg(Color::White),
|
||||
};
|
||||
|
||||
ListItem::new(content).style(style)
|
||||
})
|
||||
@@ -72,9 +85,75 @@ pub fn render(f: &mut Frame, area: Rect, app: &mut App) {
|
||||
|
||||
f.render_stateful_widget(chats_list, chat_chunks[1], &mut app.chat_list_state);
|
||||
|
||||
// User status
|
||||
let status = Paragraph::new("[User: Online]")
|
||||
// User status - показываем статус выбранного чата
|
||||
let (status_text, status_color) = if let Some(chat_id) = app.selected_chat_id {
|
||||
match app.td_client.get_user_status_by_chat_id(chat_id) {
|
||||
Some(UserOnlineStatus::Online) => ("● онлайн".to_string(), Color::Green),
|
||||
Some(UserOnlineStatus::Recently) => ("был(а) недавно".to_string(), Color::Yellow),
|
||||
Some(UserOnlineStatus::Offline(was_online)) => {
|
||||
let formatted = format_was_online(*was_online);
|
||||
(formatted, Color::Gray)
|
||||
}
|
||||
Some(UserOnlineStatus::LastWeek) => ("был(а) на этой неделе".to_string(), Color::DarkGray),
|
||||
Some(UserOnlineStatus::LastMonth) => ("был(а) в этом месяце".to_string(), Color::DarkGray),
|
||||
Some(UserOnlineStatus::LongTimeAgo) => ("был(а) давно".to_string(), Color::DarkGray),
|
||||
None => ("".to_string(), Color::DarkGray), // Для групп/каналов
|
||||
}
|
||||
} else {
|
||||
// Показываем статус выделенного в списке чата
|
||||
let filtered = app.get_filtered_chats();
|
||||
if let Some(i) = app.chat_list_state.selected() {
|
||||
if let Some(chat) = filtered.get(i) {
|
||||
match app.td_client.get_user_status_by_chat_id(chat.id) {
|
||||
Some(UserOnlineStatus::Online) => ("● онлайн".to_string(), Color::Green),
|
||||
Some(UserOnlineStatus::Recently) => ("был(а) недавно".to_string(), Color::Yellow),
|
||||
Some(UserOnlineStatus::Offline(was_online)) => {
|
||||
let formatted = format_was_online(*was_online);
|
||||
(formatted, Color::Gray)
|
||||
}
|
||||
Some(UserOnlineStatus::LastWeek) => ("был(а) на этой неделе".to_string(), Color::DarkGray),
|
||||
Some(UserOnlineStatus::LastMonth) => ("был(а) в этом месяце".to_string(), Color::DarkGray),
|
||||
Some(UserOnlineStatus::LongTimeAgo) => ("был(а) давно".to_string(), Color::DarkGray),
|
||||
None => ("".to_string(), Color::DarkGray),
|
||||
}
|
||||
} else {
|
||||
("".to_string(), Color::DarkGray)
|
||||
}
|
||||
} else {
|
||||
("".to_string(), Color::DarkGray)
|
||||
}
|
||||
};
|
||||
|
||||
let status = Paragraph::new(status_text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.style(Style::default().fg(Color::Green));
|
||||
.style(Style::default().fg(status_color));
|
||||
f.render_widget(status, chat_chunks[2]);
|
||||
}
|
||||
|
||||
/// Форматирование времени "был(а) в ..."
|
||||
fn format_was_online(timestamp: i32) -> String {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as i32;
|
||||
|
||||
let diff = now - timestamp;
|
||||
|
||||
if diff < 60 {
|
||||
"был(а) только что".to_string()
|
||||
} else if diff < 3600 {
|
||||
let mins = diff / 60;
|
||||
format!("был(а) {} мин. назад", mins)
|
||||
} else if diff < 86400 {
|
||||
let hours = diff / 3600;
|
||||
format!("был(а) {} ч. назад", hours)
|
||||
} else {
|
||||
// Показываем дату
|
||||
let datetime = chrono::DateTime::from_timestamp(timestamp as i64, 0)
|
||||
.map(|dt| dt.format("%d.%m %H:%M").to_string())
|
||||
.unwrap_or_else(|| "давно".to_string());
|
||||
format!("был(а) {}", datetime)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,19 +36,29 @@ pub fn render(f: &mut Frame, app: &mut App) {
|
||||
fn render_folders(f: &mut Frame, area: Rect, app: &App) {
|
||||
let mut spans = vec![];
|
||||
|
||||
for (i, folder) in app.folders.iter().enumerate() {
|
||||
let style = if i == app.selected_folder {
|
||||
// "All" всегда первая (клавиша 1)
|
||||
let all_style = if app.selected_folder_id.is_none() {
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::White)
|
||||
};
|
||||
spans.push(Span::styled(" 1:All ", all_style));
|
||||
|
||||
// Папки из TDLib (клавиши 2, 3, 4...)
|
||||
for (i, folder) in app.td_client.folders.iter().enumerate() {
|
||||
spans.push(Span::raw("│"));
|
||||
|
||||
let style = if app.selected_folder_id == Some(folder.id) {
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::White)
|
||||
};
|
||||
|
||||
spans.push(Span::styled(format!(" {}:{} ", i + 1, folder), style));
|
||||
if i < app.folders.len() - 1 {
|
||||
spans.push(Span::raw("│"));
|
||||
}
|
||||
spans.push(Span::styled(format!(" {}:{} ", i + 2, folder.name), style));
|
||||
}
|
||||
|
||||
let folders_line = Line::from(spans);
|
||||
|
||||
Reference in New Issue
Block a user