fixes
Some checks 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
Some checks 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
This commit is contained in:
210
src/ui/modals/account_switcher.rs
Normal file
210
src/ui/modals/account_switcher.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! 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);
|
||||
}
|
||||
Reference in New Issue
Block a user