Files
telegram-tui/src/ui/modals/account_switcher.rs
Mikhail Kilin 264f183510
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
style: auto-format entire codebase with cargo fmt (stable rustfmt.toml)
2026-02-22 17:09:51 +03:00

191 lines
6.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Account switcher modal
//!
//! Renders a centered popup with account list (SelectAccount) or
//! new account name input (AddAccount).
use crate::app::{AccountSwitcherState, App};
use crate::tdlib::TdClientTrait;
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, Paragraph},
Frame,
};
/// Renders the account switcher modal overlay.
pub fn render<T: TdClientTrait>(f: &mut Frame, area: Rect, app: &App<T>) {
let Some(state) = &app.account_switcher else {
return;
};
match state {
AccountSwitcherState::SelectAccount { accounts, selected_index, current_account } => {
render_select_account(f, area, accounts, *selected_index, current_account);
}
AccountSwitcherState::AddAccount { name_input, cursor_position, error } => {
render_add_account(f, area, name_input, *cursor_position, error.as_deref());
}
}
}
fn render_select_account(
f: &mut Frame,
area: Rect,
accounts: &[crate::accounts::AccountProfile],
selected_index: usize,
current_account: &str,
) {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(""));
for (idx, account) in accounts.iter().enumerate() {
let is_selected = idx == selected_index;
let is_current = account.name == current_account;
let marker = if is_current { "" } else { " " };
let suffix = if is_current { " (текущий)" } else { "" };
let display = format!("{}{} ({}){}", marker, account.name, account.display_name, suffix);
let style = if is_selected {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else if is_current {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::White)
};
lines.push(Line::from(Span::styled(format!(" {}", display), style)));
}
// Separator
lines.push(Line::from(Span::styled(
" ──────────────────────",
Style::default().fg(Color::DarkGray),
)));
// Add account item
let add_selected = selected_index == accounts.len();
let add_style = if add_selected {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::Cyan)
};
lines.push(Line::from(Span::styled(" + Добавить аккаунт", add_style)));
lines.push(Line::from(""));
// Help bar
lines.push(Line::from(vec![
Span::styled(" j/k ", Style::default().fg(Color::Yellow)),
Span::styled("Nav", Style::default().fg(Color::DarkGray)),
Span::raw(" "),
Span::styled(" Enter ", Style::default().fg(Color::Green)),
Span::styled("Select", Style::default().fg(Color::DarkGray)),
Span::raw(" "),
Span::styled(" a ", Style::default().fg(Color::Cyan)),
Span::styled("Add", Style::default().fg(Color::DarkGray)),
Span::raw(" "),
Span::styled(" Esc ", Style::default().fg(Color::Red)),
Span::styled("Close", Style::default().fg(Color::DarkGray)),
]));
// Calculate dynamic height: header(3) + accounts + separator(1) + add(1) + empty(1) + help(1) + footer(1)
let content_height = (accounts.len() as u16) + 7;
let height = content_height.min(area.height.saturating_sub(4));
let width = 40u16.min(area.width.saturating_sub(4));
let x = area.x + (area.width.saturating_sub(width)) / 2;
let y = area.y + (area.height.saturating_sub(height)) / 2;
let modal_area = Rect::new(x, y, width, height);
f.render_widget(Clear, modal_area);
let modal = Paragraph::new(lines).block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan))
.title(" АККАУНТЫ ")
.title_style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
);
f.render_widget(modal, modal_area);
}
fn render_add_account(
f: &mut Frame,
area: Rect,
name_input: &str,
_cursor_position: usize,
error: Option<&str>,
) {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(""));
// Input field
let input_display = if name_input.is_empty() {
Span::styled("_", Style::default().fg(Color::DarkGray))
} else {
Span::styled(format!("{}_", name_input), Style::default().fg(Color::White))
};
lines.push(Line::from(vec![
Span::styled(" Имя: ", Style::default().fg(Color::Cyan)),
input_display,
]));
// Hint
lines.push(Line::from(Span::styled(
" (a-z, 0-9, -, _)",
Style::default().fg(Color::DarkGray),
)));
lines.push(Line::from(""));
// Error
if let Some(err) = error {
lines.push(Line::from(Span::styled(format!(" {}", err), Style::default().fg(Color::Red))));
lines.push(Line::from(""));
}
// Help bar
lines.push(Line::from(vec![
Span::styled(" Enter ", Style::default().fg(Color::Green)),
Span::styled("Create", Style::default().fg(Color::DarkGray)),
Span::raw(" "),
Span::styled(" Esc ", Style::default().fg(Color::Red)),
Span::styled("Back", Style::default().fg(Color::DarkGray)),
]));
let height = if error.is_some() { 10 } else { 8 };
let height = (height as u16).min(area.height.saturating_sub(4));
let width = 40u16.min(area.width.saturating_sub(4));
let x = area.x + (area.width.saturating_sub(width)) / 2;
let y = area.y + (area.height.saturating_sub(height)) / 2;
let modal_area = Rect::new(x, y, width, height);
f.render_widget(Clear, modal_area);
let modal = Paragraph::new(lines).block(
Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan))
.title(" НОВЫЙ АККАУНТ ")
.title_style(
Style::default()
.fg(Color::Cyan)
.add_modifier(Modifier::BOLD),
),
);
f.render_widget(modal, modal_area);
}