use ratatui::{
Frame,
prelude::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
};
use crate::i18n;
use crate::state::{AppState, PackageItem};
use crate::theme::theme;
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_install(f: &mut Frame, app: &AppState, area: Rect, items: &[PackageItem]) {
let th = theme();
let w = area.width.saturating_sub(6).min(90);
let h = area.height.saturating_sub(6).min(20);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = ratatui::prelude::Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.heading"),
Style::default().fg(th.mauve).add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(""));
if items.is_empty() {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.none"),
Style::default().fg(th.subtext1),
)));
} else {
for p in items.iter().take((h as usize).saturating_sub(6)) {
let p_name = &p.name;
lines.push(Line::from(Span::styled(
format!("- {p_name}"),
Style::default().fg(th.text),
)));
}
if items.len() + 6 > h as usize {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.list_ellipsis"),
Style::default().fg(th.subtext1),
)));
}
}
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.confirm_hint"),
Style::default().fg(th.subtext1),
)));
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.scan_hint"),
Style::default().fg(th.overlay1),
)));
let boxw = Paragraph::new(lines)
.style(Style::default().fg(th.text).bg(th.mantle))
.wrap(Wrap { trim: true })
.block(
Block::default()
.title(Span::styled(
i18n::t(app, "app.modals.confirm_install.title"),
Style::default().fg(th.mauve).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(th.mauve))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(boxw, rect);
}
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_remove(f: &mut Frame, app: &AppState, area: Rect, items: &[PackageItem]) {
let th = theme();
let w = area.width.saturating_sub(6).min(90);
let h = area.height.saturating_sub(6).min(20);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = ratatui::prelude::Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_remove.heading"),
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(""));
let has_core = items.iter().any(|p| match &p.source {
crate::state::Source::Official { repo, .. } => repo.eq_ignore_ascii_case("core"),
crate::state::Source::Aur => false,
});
if has_core {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_remove.warning_core"),
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(""));
}
if items.is_empty() {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_remove.none"),
Style::default().fg(th.subtext1),
)));
} else {
for p in items.iter().take((h as usize).saturating_sub(6)) {
let p_name = &p.name;
lines.push(Line::from(Span::styled(
format!("- {p_name}"),
Style::default().fg(th.text),
)));
}
if items.len() + 6 > h as usize {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_install.list_ellipsis"),
Style::default().fg(th.subtext1),
)));
}
}
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_remove.warning_removal"),
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
)));
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_remove.confirm_hint"),
Style::default().fg(th.subtext1),
)));
let boxw = Paragraph::new(lines)
.style(Style::default().fg(th.text).bg(th.mantle))
.wrap(Wrap { trim: true })
.block(
Block::default()
.title(Span::styled(
i18n::t(app, "app.modals.confirm_remove.title"),
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(th.red))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(boxw, rect);
}
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_batch_update(
f: &mut Frame,
_app: &AppState,
area: Rect,
items: &[PackageItem],
) {
let th = theme();
let w = area.width.saturating_sub(6).min(90);
let h = area.height.saturating_sub(6).min(20);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = ratatui::prelude::Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let mut lines = vec![
Line::from(Span::styled(
"Warning: Single package updates may break package, due to dependency conflicts.",
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(Span::styled(
"Do you want to continue?",
Style::default().fg(th.text),
)),
Line::from(""),
];
if !items.is_empty() {
lines.push(Line::from(Span::styled(
"Packages to update:",
Style::default().fg(th.subtext1),
)));
for p in items.iter().take((h as usize).saturating_sub(8)) {
let p_name = &p.name;
lines.push(Line::from(Span::styled(
format!(" - {p_name}"),
Style::default().fg(th.text),
)));
}
if items.len() + 8 > h as usize {
lines.push(Line::from(Span::styled(
"...",
Style::default().fg(th.subtext1),
)));
}
lines.push(Line::from(""));
}
lines.push(Line::from(Span::styled(
"Press Enter to continue, Esc/q to cancel",
Style::default().fg(th.subtext1),
)));
let boxw = Paragraph::new(lines)
.style(Style::default().fg(th.text).bg(th.mantle))
.wrap(Wrap { trim: true })
.block(
Block::default()
.title(Span::styled(
"Confirm Batch Update",
Style::default().fg(th.red).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(th.red))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(boxw, rect);
}
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_aur_update(f: &mut Frame, app: &AppState, area: Rect, message: &str) {
let th = theme();
let w = area.width.saturating_sub(6).min(90);
let h = area.height.saturating_sub(6).min(20);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let lines: Vec<Line> = message
.lines()
.map(|l| Line::from(Span::styled(l, Style::default().fg(th.text))))
.collect();
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: true }).block(
Block::default()
.title(Span::styled(
i18n::t(app, "app.modals.confirm_aur_update.title"),
Style::default().fg(th.yellow).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(th.yellow))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(paragraph, rect);
}
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_aur_vote(
f: &mut Frame,
_app: &AppState,
area: Rect,
action: crate::sources::VoteAction,
message: &str,
) {
let th = theme();
let w = area.width.saturating_sub(6).min(90);
let h = area.height.saturating_sub(6).min(20);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let lines: Vec<Line> = message
.lines()
.map(|line| Line::from(Span::styled(line, Style::default().fg(th.text))))
.collect();
let (title, accent) = match action {
crate::sources::VoteAction::Vote => ("Confirm AUR Vote", th.yellow),
crate::sources::VoteAction::Unvote => ("Confirm AUR Unvote", th.red),
};
let paragraph = Paragraph::new(lines).wrap(Wrap { trim: true }).block(
Block::default()
.title(Span::styled(
title,
Style::default().fg(accent).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(accent))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(paragraph, rect);
}
#[allow(clippy::many_single_char_names)]
pub fn render_confirm_reinstall(f: &mut Frame, app: &AppState, area: Rect, items: &[PackageItem]) {
let th = theme();
let base_lines = 6u16;
let content_lines = if items.is_empty() {
1u16 } else {
let package_count = items.len().min(5);
u16::try_from(package_count + usize::from(items.len() > 5)).unwrap_or(6)
};
let required_height = base_lines + content_lines + 2;
let h = area
.height
.saturating_sub(4)
.min(required_height.clamp(10, 18));
let w = area.width.saturating_sub(6).min(65);
let x = area.x + (area.width.saturating_sub(w)) / 2;
let y = area.y + (area.height.saturating_sub(h)) / 2;
let rect = ratatui::prelude::Rect {
x,
y,
width: w,
height: h,
};
f.render_widget(Clear, rect);
let mut lines = vec![
Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_reinstall.heading"),
Style::default().fg(th.yellow).add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_reinstall.message"),
Style::default().fg(th.subtext1),
)),
Line::from(""),
];
if items.is_empty() {
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_reinstall.none"),
Style::default().fg(th.subtext1),
)));
} else {
for p in items.iter().take(5) {
let p_name = &p.name;
lines.push(Line::from(Span::styled(
format!(" • {p_name}"),
Style::default().fg(th.text),
)));
}
if items.len() > 5 {
let remaining = items.len() - 5;
lines.push(Line::from(Span::styled(
i18n::t_fmt1(app, "app.modals.confirm_reinstall.and_more", remaining),
Style::default().fg(th.subtext1),
)));
}
}
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
i18n::t(app, "app.modals.confirm_reinstall.confirm_hint"),
Style::default()
.fg(th.subtext1)
.add_modifier(Modifier::BOLD),
)));
let boxw = Paragraph::new(lines)
.style(Style::default().fg(th.text).bg(th.mantle))
.wrap(Wrap { trim: true })
.block(
Block::default()
.title(Span::styled(
i18n::t(app, "app.modals.confirm_reinstall.title"),
Style::default().fg(th.yellow).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Double)
.border_style(Style::default().fg(th.yellow))
.style(Style::default().bg(th.mantle)),
);
f.render_widget(boxw, rect);
}