refactor: extract header and pinned bar rendering (ui/messages.rs)

Извлечены функции из render() (Phase 5 начало):
- render_chat_header() - заголовок чата с typing status (~50 строк)
- render_pinned_bar() - панель закреплённого сообщения (~30 строк)

Результат:
- Главная функция render() сокращена на ~65 строк
- Применены let-else guards для упрощения
- Каждая функция имеет чёткую ответственность

Phase 5: Рефакторинг ui/messages.rs (879 строк)
Цель: разделить монолитную функцию render() на модули

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-03 20:41:08 +03:00
parent 6150fe3cdb
commit 315395f1f2

View File

@@ -10,6 +10,84 @@ use ratatui::{
Frame, Frame,
}; };
/// Рендерит заголовок чата с typing status
fn render_chat_header<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>, chat: &crate::tdlib::ChatInfo) {
let typing_action = app
.td_client
.typing_status()
.as_ref()
.map(|(_, action, _)| action.clone());
let header_line = if let Some(action) = typing_action {
// Показываем typing status: "👤 Имя @username печатает..."
let mut spans = vec![Span::styled(
format!("👤 {}", chat.title),
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)];
if let Some(username) = &chat.username {
spans.push(Span::styled(
format!(" {}", username),
Style::default().fg(Color::Gray),
));
}
spans.push(Span::styled(
format!(" {}", action),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::ITALIC),
));
Line::from(spans)
} else {
// Показываем username
let header_text = match &chat.username {
Some(username) => format!("👤 {} {}", chat.title, username),
None => format!("👤 {}", chat.title),
};
Line::from(Span::styled(
header_text,
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
))
};
let header = Paragraph::new(header_line).block(Block::default().borders(Borders::ALL));
f.render_widget(header, area);
}
/// Рендерит pinned bar с закреплённым сообщением
fn render_pinned_bar<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
let Some(pinned_msg) = app.td_client.current_pinned_message() else {
return;
};
let pinned_preview: String = pinned_msg.text().chars().take(40).collect();
let ellipsis = if pinned_msg.text().chars().count() > 40 {
"..."
} else {
""
};
let pinned_datetime = crate::utils::format_datetime(pinned_msg.date());
let pinned_text = format!("📌 {} {}{}", pinned_datetime, pinned_preview, ellipsis);
let pinned_hint = "Ctrl+P";
let pinned_bar_width = area.width as usize;
let text_len = pinned_text.chars().count();
let hint_len = pinned_hint.chars().count();
let padding = pinned_bar_width.saturating_sub(text_len + hint_len + 2);
let pinned_line = Line::from(vec![
Span::styled(pinned_text, Style::default().fg(Color::Magenta)),
Span::raw(" ".repeat(padding)),
Span::styled(pinned_hint, Style::default().fg(Color::Gray)),
]);
let pinned_bar =
Paragraph::new(pinned_line).style(Style::default().bg(Color::Rgb(40, 20, 40)));
f.render_widget(pinned_bar, area);
}
fn render_input_with_cursor( fn render_input_with_cursor(
prefix: &str, prefix: &str,
text: &str, text: &str,
@@ -173,72 +251,10 @@ pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
}; };
// Chat header с typing status // Chat header с typing status
let typing_action = app render_chat_header(f, message_chunks[0], app, chat);
.td_client
.typing_status()
.as_ref()
.map(|(_, action, _)| action.clone());
let header_line = if let Some(action) = typing_action {
// Показываем typing status: "👤 Имя @username печатает..."
let mut spans = vec![Span::styled(
format!("👤 {}", chat.title),
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
)];
if let Some(username) = &chat.username {
spans
.push(Span::styled(format!(" {}", username), Style::default().fg(Color::Gray)));
}
spans.push(Span::styled(
format!(" {}", action),
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::ITALIC),
));
Line::from(spans)
} else {
// Показываем username
let header_text = match &chat.username {
Some(username) => format!("👤 {} {}", chat.title, username),
None => format!("👤 {}", chat.title),
};
Line::from(Span::styled(
header_text,
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
))
};
let header = Paragraph::new(header_line).block(Block::default().borders(Borders::ALL));
f.render_widget(header, message_chunks[0]);
// Pinned bar (если есть закреплённое сообщение) // Pinned bar (если есть закреплённое сообщение)
if let Some(pinned_msg) = app.td_client.current_pinned_message() { render_pinned_bar(f, message_chunks[1], app);
let pinned_preview: String = pinned_msg.text().chars().take(40).collect();
let ellipsis = if pinned_msg.text().chars().count() > 40 {
"..."
} else {
""
};
let pinned_datetime = crate::utils::format_datetime(pinned_msg.date());
let pinned_text = format!("📌 {} {}{}", pinned_datetime, pinned_preview, ellipsis);
let pinned_hint = "Ctrl+P";
let pinned_bar_width = message_chunks[1].width as usize;
let text_len = pinned_text.chars().count();
let hint_len = pinned_hint.chars().count();
let padding = pinned_bar_width.saturating_sub(text_len + hint_len + 2);
let pinned_line = Line::from(vec![
Span::styled(pinned_text, Style::default().fg(Color::Magenta)),
Span::raw(" ".repeat(padding)),
Span::styled(pinned_hint, Style::default().fg(Color::Gray)),
]);
let pinned_bar =
Paragraph::new(pinned_line).style(Style::default().bg(Color::Rgb(40, 20, 40)));
f.render_widget(pinned_bar, message_chunks[1]);
}
// Ширина области сообщений (без рамок) // Ширина области сообщений (без рамок)
let content_width = message_chunks[2].width.saturating_sub(2) as usize; let content_width = message_chunks[2].width.saturating_sub(2) as usize;