add account profile

This commit is contained in:
Mikhail Kilin
2026-01-27 13:41:29 +03:00
parent ac684da820
commit 356d2d3064
15 changed files with 787 additions and 26 deletions

259
src/ui/profile.rs Normal file
View File

@@ -0,0 +1,259 @@
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]
}