Merge pull request 'fixes' (#3) from yet-another-changes into main
Reviewed-on: #3
This commit is contained in:
18
src/main.rs
18
src/main.rs
@@ -109,12 +109,22 @@ async fn run_app<B: ratatui::backend::Backend>(
|
|||||||
app.td_client.process_pending_view_messages().await;
|
app.td_client.process_pending_view_messages().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обрабатываем очередь user_id для загрузки имён
|
||||||
|
if !app.td_client.pending_user_ids.is_empty() {
|
||||||
|
app.td_client.process_pending_user_ids().await;
|
||||||
|
}
|
||||||
|
|
||||||
// Синхронизируем сообщения из td_client в app (для новых сообщений в реальном времени)
|
// Синхронизируем сообщения из td_client в app (для новых сообщений в реальном времени)
|
||||||
if app.selected_chat_id.is_some() && !app.td_client.current_chat_messages.is_empty() {
|
if app.selected_chat_id.is_some() && !app.td_client.current_chat_messages.is_empty() {
|
||||||
// Добавляем новые сообщения, которых ещё нет в app.current_messages
|
// Синхронизируем все сообщения (включая обновлённые имена и is_read)
|
||||||
for msg in &app.td_client.current_chat_messages {
|
for td_msg in &app.td_client.current_chat_messages {
|
||||||
if !app.current_messages.iter().any(|m| m.id == msg.id) {
|
if let Some(app_msg) = app.current_messages.iter_mut().find(|m| m.id == td_msg.id) {
|
||||||
app.current_messages.push(msg.clone());
|
// Обновляем существующее сообщение
|
||||||
|
app_msg.sender_name = td_msg.sender_name.clone();
|
||||||
|
app_msg.is_read = td_msg.is_read;
|
||||||
|
} else {
|
||||||
|
// Добавляем новое сообщение
|
||||||
|
app.current_messages.push(td_msg.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,14 @@ pub struct TdClient {
|
|||||||
pub current_chat_id: Option<i64>,
|
pub current_chat_id: Option<i64>,
|
||||||
/// Кэш usernames: user_id -> username
|
/// Кэш usernames: user_id -> username
|
||||||
user_usernames: HashMap<i64, String>,
|
user_usernames: HashMap<i64, String>,
|
||||||
|
/// Кэш имён: user_id -> display_name (first_name + last_name)
|
||||||
|
user_names: HashMap<i64, String>,
|
||||||
/// Связь chat_id -> user_id для приватных чатов
|
/// Связь chat_id -> user_id для приватных чатов
|
||||||
chat_user_ids: HashMap<i64, i64>,
|
chat_user_ids: HashMap<i64, i64>,
|
||||||
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
|
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
|
||||||
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
|
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
|
||||||
|
/// Очередь user_id для загрузки имён
|
||||||
|
pub pending_user_ids: Vec<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -78,8 +82,10 @@ impl TdClient {
|
|||||||
current_chat_messages: Vec::new(),
|
current_chat_messages: Vec::new(),
|
||||||
current_chat_id: None,
|
current_chat_id: None,
|
||||||
user_usernames: HashMap::new(),
|
user_usernames: HashMap::new(),
|
||||||
|
user_names: HashMap::new(),
|
||||||
chat_user_ids: HashMap::new(),
|
chat_user_ids: HashMap::new(),
|
||||||
pending_view_messages: Vec::new(),
|
pending_view_messages: Vec::new(),
|
||||||
|
pending_user_ids: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,8 +217,18 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Update::User(update) => {
|
Update::User(update) => {
|
||||||
// Сохраняем username пользователя
|
// Сохраняем имя и username пользователя
|
||||||
let user = update.user;
|
let user = update.user;
|
||||||
|
|
||||||
|
// Сохраняем display name (first_name + last_name)
|
||||||
|
let display_name = if user.last_name.is_empty() {
|
||||||
|
user.first_name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{} {}", user.first_name, user.last_name)
|
||||||
|
};
|
||||||
|
self.user_names.insert(user.id, display_name);
|
||||||
|
|
||||||
|
// Сохраняем username если есть
|
||||||
if let Some(usernames) = user.usernames {
|
if let Some(usernames) = user.usernames {
|
||||||
if let Some(username) = usernames.active_usernames.first() {
|
if let Some(username) = usernames.active_usernames.first() {
|
||||||
self.user_usernames.insert(user.id, username.clone());
|
self.user_usernames.insert(user.id, username.clone());
|
||||||
@@ -305,10 +321,28 @@ impl TdClient {
|
|||||||
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_message(&self, message: &TdMessage, chat_id: i64) -> MessageInfo {
|
fn convert_message(&mut self, message: &TdMessage, chat_id: i64) -> MessageInfo {
|
||||||
let sender_name = match &message.sender_id {
|
let sender_name = match &message.sender_id {
|
||||||
tdlib_rs::enums::MessageSender::User(user) => format!("User_{}", user.user_id),
|
tdlib_rs::enums::MessageSender::User(user) => {
|
||||||
tdlib_rs::enums::MessageSender::Chat(chat) => format!("Chat_{}", chat.chat_id),
|
// Пробуем получить имя из кеша
|
||||||
|
if let Some(name) = self.user_names.get(&user.user_id) {
|
||||||
|
name.clone()
|
||||||
|
} else {
|
||||||
|
// Добавляем в очередь для загрузки
|
||||||
|
if !self.pending_user_ids.contains(&user.user_id) {
|
||||||
|
self.pending_user_ids.push(user.user_id);
|
||||||
|
}
|
||||||
|
format!("User_{}", user.user_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tdlib_rs::enums::MessageSender::Chat(chat) => {
|
||||||
|
// Для чатов используем название чата
|
||||||
|
self.chats
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.id == chat.chat_id)
|
||||||
|
.map(|c| c.title.clone())
|
||||||
|
.unwrap_or_else(|| format!("Chat_{}", chat.chat_id))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Определяем, прочитано ли исходящее сообщение
|
// Определяем, прочитано ли исходящее сообщение
|
||||||
@@ -412,11 +446,10 @@ impl TdClient {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
||||||
let batch: Vec<MessageInfo> = messages
|
let mut batch: Vec<MessageInfo> = Vec::new();
|
||||||
.messages
|
for m in messages.messages.into_iter().flatten() {
|
||||||
.into_iter()
|
batch.push(self.convert_message(&m, chat_id));
|
||||||
.filter_map(|m| m.map(|msg| self.convert_message(&msg, chat_id)))
|
}
|
||||||
.collect();
|
|
||||||
|
|
||||||
if batch.is_empty() {
|
if batch.is_empty() {
|
||||||
break;
|
break;
|
||||||
@@ -484,11 +517,10 @@ impl TdClient {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
||||||
let mut result_messages: Vec<MessageInfo> = messages
|
let mut result_messages: Vec<MessageInfo> = Vec::new();
|
||||||
.messages
|
for m in messages.messages.into_iter().flatten() {
|
||||||
.into_iter()
|
result_messages.push(self.convert_message(&m, chat_id));
|
||||||
.filter_map(|m| m.map(|msg| self.convert_message(&msg, chat_id)))
|
}
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Сообщения приходят от новых к старым, переворачиваем
|
// Сообщения приходят от новых к старым, переворачиваем
|
||||||
result_messages.reverse();
|
result_messages.reverse();
|
||||||
@@ -585,6 +617,33 @@ impl TdClient {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Обработка очереди user_id для загрузки имён
|
||||||
|
pub async fn process_pending_user_ids(&mut self) {
|
||||||
|
let pending = std::mem::take(&mut self.pending_user_ids);
|
||||||
|
for user_id in pending {
|
||||||
|
// Пропускаем если имя уже есть
|
||||||
|
if self.user_names.contains_key(&user_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Загружаем информацию о пользователе
|
||||||
|
if let Ok(User::User(user)) = functions::get_user(user_id, self.client_id).await {
|
||||||
|
let display_name = if user.last_name.is_empty() {
|
||||||
|
user.first_name.clone()
|
||||||
|
} else {
|
||||||
|
format!("{} {}", user.first_name, user.last_name)
|
||||||
|
};
|
||||||
|
self.user_names.insert(user_id, display_name.clone());
|
||||||
|
|
||||||
|
// Обновляем имя в текущих сообщениях
|
||||||
|
for msg in &mut self.current_chat_messages {
|
||||||
|
if msg.sender_name == format!("User_{}", user_id) {
|
||||||
|
msg.sender_name = display_name.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Статическая функция для извлечения текста сообщения (без &self)
|
/// Статическая функция для извлечения текста сообщения (без &self)
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
);
|
);
|
||||||
f.render_widget(header, message_chunks[0]);
|
f.render_widget(header, message_chunks[0]);
|
||||||
|
|
||||||
|
// Ширина области сообщений (без рамок)
|
||||||
|
let content_width = message_chunks[1].width.saturating_sub(2) as usize;
|
||||||
|
|
||||||
// Messages с группировкой по дате и отправителю
|
// Messages с группировкой по дате и отправителю
|
||||||
let mut lines: Vec<Line> = Vec::new();
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
let mut last_day: Option<i64> = None;
|
let mut last_day: Option<i64> = None;
|
||||||
@@ -45,13 +48,13 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
if last_day.is_some() {
|
if last_day.is_some() {
|
||||||
lines.push(Line::from("")); // Пустая строка перед разделителем
|
lines.push(Line::from("")); // Пустая строка перед разделителем
|
||||||
}
|
}
|
||||||
// Добавляем разделитель даты
|
// Добавляем разделитель даты по центру
|
||||||
let date_str = format_date(msg.date);
|
let date_str = format_date(msg.date);
|
||||||
|
let date_line = format!("──────── {} ────────", date_str);
|
||||||
|
let padding = content_width.saturating_sub(date_line.chars().count()) / 2;
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(
|
Span::raw(" ".repeat(padding)),
|
||||||
format!("──────── {} ────────", date_str),
|
Span::styled(date_line, Style::default().fg(Color::Gray)),
|
||||||
Style::default().fg(Color::DarkGray),
|
|
||||||
),
|
|
||||||
]));
|
]));
|
||||||
lines.push(Line::from(""));
|
lines.push(Line::from(""));
|
||||||
last_day = Some(msg_day);
|
last_day = Some(msg_day);
|
||||||
@@ -76,20 +79,28 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sender_style = if msg.is_outgoing {
|
let sender_style = if msg.is_outgoing {
|
||||||
Style::default()
|
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
|
||||||
.fg(Color::Green)
|
|
||||||
.add_modifier(Modifier::BOLD)
|
|
||||||
} else {
|
} else {
|
||||||
Style::default()
|
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||||
.fg(Color::Cyan)
|
|
||||||
.add_modifier(Modifier::BOLD)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Заголовок отправителя
|
if msg.is_outgoing {
|
||||||
|
// Заголовок "Вы" справа
|
||||||
|
let header_text = format!("{} ────────────────", sender_name);
|
||||||
|
let header_len = header_text.chars().count();
|
||||||
|
let padding = content_width.saturating_sub(header_len + 1);
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::raw(" ".repeat(padding)),
|
||||||
|
Span::styled(format!("{} ", sender_name), sender_style),
|
||||||
|
Span::styled("────────────────", Style::default().fg(Color::Gray)),
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
// Заголовок входящих слева
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(format!("{} ", sender_name), sender_style),
|
Span::styled(format!("{} ", sender_name), sender_style),
|
||||||
Span::styled("────────────────", Style::default().fg(Color::DarkGray)),
|
Span::styled("────────────────", Style::default().fg(Color::Gray)),
|
||||||
]));
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
last_sender = Some(current_sender);
|
last_sender = Some(current_sender);
|
||||||
}
|
}
|
||||||
@@ -97,24 +108,33 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
// Форматируем время (HH:MM)
|
// Форматируем время (HH:MM)
|
||||||
let time = format_timestamp(msg.date);
|
let time = format_timestamp(msg.date);
|
||||||
|
|
||||||
let read_mark = if msg.is_outgoing {
|
if msg.is_outgoing {
|
||||||
if msg.is_read { " ✓✓" } else { " ✓" }
|
// Исходящие: справа, формат "текст (HH:MM ✓✓)"
|
||||||
} else {
|
let read_mark = if msg.is_read { "✓✓" } else { "✓" };
|
||||||
""
|
let time_mark = format!("({} {})", time, read_mark);
|
||||||
};
|
let msg_text = format!("{} {}", msg.content, time_mark);
|
||||||
|
let msg_len = msg_text.chars().count();
|
||||||
|
let padding = content_width.saturating_sub(msg_len + 1);
|
||||||
|
|
||||||
// Сообщение с временем
|
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled(format!(" [{}]", time), Style::default().fg(Color::DarkGray)),
|
Span::raw(" ".repeat(padding)),
|
||||||
Span::raw(format!(" {}", msg.content)),
|
Span::styled(msg.content.clone(), Style::default().fg(Color::Green)),
|
||||||
Span::styled(read_mark.to_string(), Style::default().fg(Color::DarkGray)),
|
Span::styled(format!(" {}", time_mark), Style::default().fg(Color::Gray)),
|
||||||
]));
|
]));
|
||||||
|
} else {
|
||||||
|
// Входящие: слева, формат "(HH:MM) текст"
|
||||||
|
let time_str = format!("({})", time);
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled(format!(" {}", time_str), Style::default().fg(Color::Gray)),
|
||||||
|
Span::raw(format!(" {}", msg.content)),
|
||||||
|
]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
lines.push(Line::from(Span::styled(
|
lines.push(Line::from(Span::styled(
|
||||||
"Нет сообщений",
|
"Нет сообщений",
|
||||||
Style::default().fg(Color::DarkGray),
|
Style::default().fg(Color::Gray),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +162,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
format!("> {}", app.message_input)
|
format!("> {}", app.message_input)
|
||||||
};
|
};
|
||||||
let input_style = if app.message_input.is_empty() {
|
let input_style = if app.message_input.is_empty() {
|
||||||
Style::default().fg(Color::DarkGray)
|
Style::default().fg(Color::Gray)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::Yellow)
|
Style::default().fg(Color::Yellow)
|
||||||
};
|
};
|
||||||
@@ -153,7 +173,7 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
|
|||||||
} else {
|
} else {
|
||||||
let empty = Paragraph::new("Выберите чат")
|
let empty = Paragraph::new("Выберите чат")
|
||||||
.block(Block::default().borders(Borders::ALL))
|
.block(Block::default().borders(Borders::ALL))
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
.style(Style::default().fg(Color::Gray))
|
||||||
.alignment(Alignment::Center);
|
.alignment(Alignment::Center);
|
||||||
f.render_widget(empty, area);
|
f.render_widget(empty, area);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user