260 lines
9.1 KiB
Rust
260 lines
9.1 KiB
Rust
use ratatui::{
|
||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||
style::{Color, Modifier, Style},
|
||
text::{Line, Span},
|
||
widgets::{Block, Borders, Paragraph},
|
||
Frame,
|
||
};
|
||
use crate::app::App;
|
||
use crate::tdlib::client::ProfileInfo;
|
||
|
||
/// Рендерит режим просмотра профиля
|
||
pub fn render(f: &mut Frame, area: Rect, app: &App, profile: &ProfileInfo) {
|
||
// Проверяем, показывать ли модалку подтверждения
|
||
let confirmation_step = app.get_leave_group_confirmation_step();
|
||
if confirmation_step > 0 {
|
||
render_leave_confirmation_modal(f, area, confirmation_step);
|
||
return;
|
||
}
|
||
|
||
let chunks = Layout::default()
|
||
.direction(Direction::Vertical)
|
||
.constraints([
|
||
Constraint::Length(3), // Header
|
||
Constraint::Min(0), // Profile info
|
||
Constraint::Length(3), // Actions help
|
||
])
|
||
.split(area);
|
||
|
||
// Header
|
||
let header_text = format!("👤 ПРОФИЛЬ: {}", profile.title);
|
||
let header = Paragraph::new(header_text)
|
||
.block(
|
||
Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_style(Style::default().fg(Color::Cyan))
|
||
)
|
||
.style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
|
||
f.render_widget(header, chunks[0]);
|
||
|
||
// Profile info
|
||
let mut lines: Vec<Line> = Vec::new();
|
||
|
||
// Тип чата
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Тип: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(&profile.chat_type, Style::default().fg(Color::White)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
|
||
// ID
|
||
lines.push(Line::from(vec![
|
||
Span::styled("ID: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(format!("{}", profile.chat_id), Style::default().fg(Color::White)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
|
||
// Username
|
||
if let Some(username) = &profile.username {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Username: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(username, Style::default().fg(Color::Cyan)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Phone number (только для личных чатов)
|
||
if let Some(phone) = &profile.phone_number {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Телефон: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(phone, Style::default().fg(Color::White)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Online status (только для личных чатов)
|
||
if let Some(status) = &profile.online_status {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Статус: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(status, Style::default().fg(Color::Green)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Bio (только для личных чатов)
|
||
if let Some(bio) = &profile.bio {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("О себе: ", Style::default().fg(Color::Gray)),
|
||
]));
|
||
// Разбиваем bio на строки если длинное
|
||
let bio_lines: Vec<&str> = bio.lines().collect();
|
||
for bio_line in bio_lines {
|
||
lines.push(Line::from(Span::styled(bio_line, Style::default().fg(Color::White))));
|
||
}
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Member count (для групп/каналов)
|
||
if let Some(count) = profile.member_count {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Участников: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(format!("{}", count), Style::default().fg(Color::White)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Description (для групп/каналов)
|
||
if let Some(desc) = &profile.description {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Описание: ", Style::default().fg(Color::Gray)),
|
||
]));
|
||
let desc_lines: Vec<&str> = desc.lines().collect();
|
||
for desc_line in desc_lines {
|
||
lines.push(Line::from(Span::styled(desc_line, Style::default().fg(Color::White))));
|
||
}
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Invite link (для групп/каналов)
|
||
if let Some(link) = &profile.invite_link {
|
||
lines.push(Line::from(vec![
|
||
Span::styled("Ссылка: ", Style::default().fg(Color::Gray)),
|
||
Span::styled(link, Style::default().fg(Color::Blue).add_modifier(Modifier::UNDERLINED)),
|
||
]));
|
||
lines.push(Line::from(""));
|
||
}
|
||
|
||
// Разделитель
|
||
lines.push(Line::from("────────────────────────────────"));
|
||
lines.push(Line::from(""));
|
||
|
||
// Действия
|
||
lines.push(Line::from(Span::styled(
|
||
"Действия:",
|
||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
|
||
)));
|
||
lines.push(Line::from(""));
|
||
|
||
let actions = get_available_actions(profile);
|
||
for (idx, action) in actions.iter().enumerate() {
|
||
let is_selected = idx == app.selected_profile_action;
|
||
let marker = if is_selected { "▶ " } else { " " };
|
||
let style = if is_selected {
|
||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||
} else {
|
||
Style::default().fg(Color::White)
|
||
};
|
||
lines.push(Line::from(vec![
|
||
Span::styled(marker, Style::default().fg(Color::Yellow)),
|
||
Span::styled(*action, style),
|
||
]));
|
||
}
|
||
|
||
let info_widget = Paragraph::new(lines)
|
||
.block(
|
||
Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_style(Style::default().fg(Color::Cyan))
|
||
)
|
||
.scroll((0, 0));
|
||
f.render_widget(info_widget, chunks[1]);
|
||
|
||
// Help bar
|
||
let help_line = Line::from(vec![
|
||
Span::styled(" ↑↓ ", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),
|
||
Span::raw("навигация"),
|
||
Span::raw(" "),
|
||
Span::styled(" Enter ", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
|
||
Span::raw("выбрать"),
|
||
Span::raw(" "),
|
||
Span::styled(" Esc ", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
|
||
Span::raw("выход"),
|
||
]);
|
||
let help = Paragraph::new(help_line)
|
||
.block(
|
||
Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_style(Style::default().fg(Color::Cyan))
|
||
)
|
||
.alignment(Alignment::Center);
|
||
f.render_widget(help, chunks[2]);
|
||
}
|
||
|
||
/// Получить список доступных действий
|
||
fn get_available_actions(profile: &ProfileInfo) -> Vec<&'static str> {
|
||
let mut actions = vec![];
|
||
|
||
if profile.username.is_some() {
|
||
actions.push("Открыть в браузере");
|
||
}
|
||
|
||
actions.push("Скопировать ID");
|
||
|
||
if profile.is_group {
|
||
actions.push("Покинуть группу");
|
||
}
|
||
|
||
actions
|
||
}
|
||
|
||
/// Рендерит модалку подтверждения выхода из группы
|
||
fn render_leave_confirmation_modal(f: &mut Frame, area: Rect, step: u8) {
|
||
// Затемняем фон
|
||
let modal_area = centered_rect(60, 30, area);
|
||
|
||
let text = if step == 1 {
|
||
"Вы хотите выйти из группы?"
|
||
} else {
|
||
"Вы ТОЧНО хотите выйти из группы?!?!?"
|
||
};
|
||
|
||
let lines = vec![
|
||
Line::from(""),
|
||
Line::from(Span::styled(
|
||
text,
|
||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
|
||
)),
|
||
Line::from(""),
|
||
Line::from(""),
|
||
Line::from(vec![
|
||
Span::styled("y/н/Enter", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
|
||
Span::raw(" — да "),
|
||
Span::styled("n/т/Esc", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
|
||
Span::raw(" — нет"),
|
||
]),
|
||
];
|
||
|
||
let modal = Paragraph::new(lines)
|
||
.block(
|
||
Block::default()
|
||
.borders(Borders::ALL)
|
||
.border_style(Style::default().fg(Color::Red))
|
||
.title(" ⚠ ВНИМАНИЕ ")
|
||
.title_style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD))
|
||
)
|
||
.alignment(Alignment::Center);
|
||
|
||
f.render_widget(modal, modal_area);
|
||
}
|
||
|
||
/// Вспомогательная функция для центрирования прямоугольника
|
||
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
||
let popup_layout = Layout::default()
|
||
.direction(Direction::Vertical)
|
||
.constraints([
|
||
Constraint::Percentage((100 - percent_y) / 2),
|
||
Constraint::Percentage(percent_y),
|
||
Constraint::Percentage((100 - percent_y) / 2),
|
||
])
|
||
.split(r);
|
||
|
||
Layout::default()
|
||
.direction(Direction::Horizontal)
|
||
.constraints([
|
||
Constraint::Percentage((100 - percent_x) / 2),
|
||
Constraint::Percentage(percent_x),
|
||
Constraint::Percentage((100 - percent_x) / 2),
|
||
])
|
||
.split(popup_layout[1])[1]
|
||
}
|