This commit is contained in:
Mikhail Kilin
2026-01-27 23:29:00 +03:00
parent 356d2d3064
commit f291191577
8 changed files with 923 additions and 43 deletions

View File

@@ -668,6 +668,58 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
}
}
}
// Отображаем реакции под сообщением
if !msg.reactions.is_empty() {
let mut reaction_spans = vec![];
for reaction in &msg.reactions {
if !reaction_spans.is_empty() {
reaction_spans.push(Span::raw(" "));
}
// Свои реакции в рамках [emoji], чужие просто emoji
let reaction_text = if reaction.is_chosen {
if reaction.count > 1 {
format!("[{}] {}", reaction.emoji, reaction.count)
} else {
format!("[{}]", reaction.emoji)
}
} else {
if reaction.count > 1 {
format!("{} {}", reaction.emoji, reaction.count)
} else {
reaction.emoji.clone()
}
};
let style = if reaction.is_chosen {
Style::default().fg(Color::Yellow)
} else {
Style::default().fg(Color::Gray)
};
reaction_spans.push(Span::styled(reaction_text, style));
}
// Выравниваем реакции в зависимости от типа сообщения
if msg.is_outgoing {
// Реакции справа для исходящих
let reactions_text: String = reaction_spans
.iter()
.map(|s| s.content.as_ref())
.collect::<Vec<_>>()
.join(" ");
let reactions_len = reactions_text.chars().count();
let padding = content_width.saturating_sub(reactions_len + 1);
let mut line_spans = vec![Span::raw(" ".repeat(padding))];
line_spans.extend(reaction_spans);
lines.push(Line::from(line_spans));
} else {
// Реакции слева для входящих
lines.push(Line::from(reaction_spans));
}
}
}
if lines.is_empty() {
@@ -734,10 +786,10 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
let can_delete = selected_msg.map(|m| m.can_be_deleted_only_for_self || m.can_be_deleted_for_all_users).unwrap_or(false);
let hint = match (can_edit, can_delete) {
(true, true) => "↑↓ · Enter ред. · r ответ · f перслть · d удал. · Esc",
(true, false) => "↑↓ · Enter ред. · r ответ · f переслть · Esc",
(false, true) => "↑↓ · r ответ · f переслать · d удалить · Esc",
(false, false) => "↑↓ · r ответить · f переслать · Esc",
(true, true) => "↑↓ · Enter ред. · r ответ · f перслть · y копир. · d удал. · Esc",
(true, false) => "↑↓ · Enter ред. · r ответ · f переслть · y копир. · Esc",
(false, true) => "↑↓ · r ответ · f переслать · y копир. · d удалить · Esc",
(false, false) => "↑↓ · r ответить · f переслать · y копировать · Esc",
};
(Line::from(Span::styled(hint, Style::default().fg(Color::Cyan))), " Выбор сообщения ")
} else if app.is_editing() {
@@ -827,6 +879,11 @@ pub fn render(f: &mut Frame, area: Rect, app: &App) {
if app.is_confirm_delete_shown() {
render_delete_confirm_modal(f, area);
}
// Модалка выбора реакции
if app.is_reaction_picker_mode() {
render_reaction_picker_modal(f, area, &app.available_reactions, app.selected_reaction_index);
}
}
/// Рендерит режим поиска по сообщениям
@@ -1136,3 +1193,78 @@ fn render_delete_confirm_modal(f: &mut Frame, area: Rect) {
f.render_widget(modal, modal_area);
}
/// Рендерит модалку выбора реакции
fn render_reaction_picker_modal(f: &mut Frame, area: Rect, available_reactions: &[String], selected_index: usize) {
use ratatui::widgets::Clear;
// Размеры модалки (зависят от количества реакций)
let emojis_per_row = 8;
let rows = (available_reactions.len() + emojis_per_row - 1) / emojis_per_row;
let modal_width = 50u16;
let modal_height = (rows + 4) as u16; // +4 для заголовка, отступов и подсказки
// Центрируем модалку
let x = area.x + (area.width.saturating_sub(modal_width)) / 2;
let y = area.y + (area.height.saturating_sub(modal_height)) / 2;
let modal_area = Rect::new(x, y, modal_width.min(area.width), modal_height.min(area.height));
// Очищаем область под модалкой
f.render_widget(Clear, modal_area);
// Формируем содержимое - сетка эмодзи
let mut text_lines = vec![Line::from("")]; // Пустая строка сверху
for row in 0..rows {
let mut row_spans = vec![Span::raw(" ")]; // Отступ слева
for col in 0..emojis_per_row {
let idx = row * emojis_per_row + col;
if idx >= available_reactions.len() {
break;
}
let emoji = &available_reactions[idx];
let is_selected = idx == selected_index;
let style = if is_selected {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::REVERSED)
} else {
Style::default().fg(Color::White)
};
row_spans.push(Span::styled(format!(" {} ", emoji), style));
row_spans.push(Span::raw(" ")); // Пробел между эмодзи
}
text_lines.push(Line::from(row_spans));
}
// Добавляем пустую строку и подсказку
text_lines.push(Line::from(""));
text_lines.push(Line::from(vec![
Span::styled(" [←/→/↑/↓] ", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
Span::raw("Выбор "),
Span::styled(" [Enter] ", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
Span::raw("Добавить "),
Span::styled(" [Esc] ", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
Span::raw("Отмена"),
]));
let modal = Paragraph::new(text_lines)
.block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Yellow))
.title(" Выбери реакцию ")
.title_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),
)
.alignment(Alignment::Left);
f.render_widget(modal, modal_area);
}