use crate::app::{App, Mode};
use crate::ui::style::{CARD_BORDER, accent_style, muted_style, primary_style};
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph};
pub struct RepoSettingsPopup;
impl RepoSettingsPopup {
pub fn handle_event(app: &mut App, key: KeyEvent) -> bool {
let code = key.code;
let repo_path = match app.get_selected_item().cloned() {
Some(p) => p,
None => return false,
};
if app.repo_settings_editing {
match code {
KeyCode::Esc => {
app.repo_settings_editing = false;
return true;
}
KeyCode::Enter => {
let val_opt = if app.repo_settings_input.is_empty() {
None
} else if let Ok(val) = app.repo_settings_input.parse::<usize>() {
Some(val)
} else {
app.repo_settings_editing = false;
return true;
};
let mut repo_cfg =
app.config.repo_configs.get(&repo_path).cloned().unwrap_or_default();
match app.repo_settings_selected_index {
1 => {
repo_cfg.page_size = val_opt;
app.config.repo_configs.insert(repo_path, repo_cfg);
app.persist("Repository page size updated");
}
2 => {
repo_cfg.max_commits = val_opt;
app.config.repo_configs.insert(repo_path, repo_cfg);
app.persist("Repository max commits updated");
}
_ => {}
}
app.repo_settings_editing = false;
return true;
}
KeyCode::Backspace => {
app.repo_settings_input.pop();
return true;
}
KeyCode::Char(c) if c.is_ascii_digit() => {
app.repo_settings_input.push(c);
return true;
}
_ => {}
}
return true;
}
match code {
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => {
app.mode = Mode::Detail;
return true;
}
KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => {
app.repo_settings_selected_index = if app.repo_settings_selected_index == 0 {
3
} else {
app.repo_settings_selected_index - 1
};
return true;
}
KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => {
app.repo_settings_selected_index = (app.repo_settings_selected_index + 1) % 4;
return true;
}
KeyCode::Left | KeyCode::Char('h') | KeyCode::Char('H') => {
Self::change_setting(app, &repo_path, false);
return true;
}
KeyCode::Right | KeyCode::Char('l') | KeyCode::Char('L') => {
Self::change_setting(app, &repo_path, true);
return true;
}
KeyCode::Enter | KeyCode::Char(' ') => {
match app.repo_settings_selected_index {
0 | 3 => {
Self::change_setting(app, &repo_path, true);
}
1 => {
let repo_cfg =
app.config.repo_configs.get(&repo_path).cloned().unwrap_or_default();
app.repo_settings_input =
repo_cfg.page_size.map(|v| v.to_string()).unwrap_or_default();
app.repo_settings_editing = true;
}
2 => {
let repo_cfg =
app.config.repo_configs.get(&repo_path).cloned().unwrap_or_default();
app.repo_settings_input =
repo_cfg.max_commits.map(|v| v.to_string()).unwrap_or_default();
app.repo_settings_editing = true;
}
_ => {}
}
return true;
}
_ => {}
}
false
}
fn change_setting(app: &mut App, repo_path: &str, right: bool) {
let mut repo_cfg = app.config.repo_configs.get(repo_path).cloned().unwrap_or_default();
match app.repo_settings_selected_index {
0 => {
let mut themes = vec!["default".to_string()];
themes.extend(app.get_available_themes());
let current_theme = repo_cfg.theme.as_deref().unwrap_or("default");
let current_idx = themes.iter().position(|t| t == current_theme).unwrap_or(0);
let next_idx = if right {
(current_idx + 1) % themes.len()
} else {
if current_idx == 0 { themes.len() - 1 } else { current_idx - 1 }
};
let new_theme = &themes[next_idx];
if new_theme == "default" {
repo_cfg.theme = None;
} else {
repo_cfg.theme = Some(new_theme.clone());
}
app.config.repo_configs.insert(repo_path.to_string(), repo_cfg);
app.persist(&format!("Repository theme set to '{}'", new_theme));
}
3 => {
let next_state = match repo_cfg.resync_on_tab_change {
None => {
if right {
Some(true)
} else {
Some(false)
}
}
Some(true) => {
if right {
Some(false)
} else {
None
}
}
Some(false) => {
if right {
None
} else {
Some(true)
}
}
};
repo_cfg.resync_on_tab_change = next_state;
let desc = match next_state {
None => "Default".to_string(),
Some(true) => "Yes".to_string(),
Some(false) => "No".to_string(),
};
app.config.repo_configs.insert(repo_path.to_string(), repo_cfg);
app.persist(&format!("Repository Resync on Tab Change set to {}", desc));
}
_ => {}
}
}
pub fn draw(f: &mut Frame, app: &App, area: Rect) {
let popup_width = 54;
let popup_height = 14;
let popup_area = crate::ui::layout::centered_rect_fixed(popup_width, popup_height, area);
let block = Block::default()
.borders(Borders::ALL)
.border_type(CARD_BORDER())
.border_style(accent_style())
.title(Span::styled(" Repository Settings ", accent_style()))
.padding(Padding::horizontal(1));
f.render_widget(Clear, popup_area);
f.render_widget(block.clone(), popup_area);
let inner = block.inner(popup_area);
let repo_path = app.get_selected_item().map(|s| s.as_str()).unwrap_or("");
let repo_name = std::path::Path::new(repo_path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(repo_path);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Min(4), Constraint::Length(1), Constraint::Length(1), ])
.split(inner);
f.render_widget(
Paragraph::new(vec![Line::from(vec![
Span::raw("Repo: "),
Span::styled(repo_name, primary_style()),
])])
.alignment(Alignment::Center),
chunks[1],
);
let repo_cfg = app
.get_selected_item()
.and_then(|p| app.config.repo_configs.get(p))
.cloned()
.unwrap_or_default();
let build_line = |idx: usize,
label: &str,
value: &str,
is_editing: bool|
-> Line<'static> {
let is_selected = idx == app.repo_settings_selected_index;
let prefix = if is_selected { "▶ " } else { " " };
let mut spans = vec![
Span::styled(prefix, if is_selected { accent_style() } else { Style::default() }),
Span::styled(
format!("{:<24}", label),
if is_selected {
primary_style().add_modifier(Modifier::BOLD)
} else {
primary_style()
},
),
];
if is_selected && is_editing {
spans.push(Span::styled("[ ", muted_style()));
spans.push(Span::styled(
value.to_string(),
primary_style().add_modifier(Modifier::BOLD),
));
spans.push(Span::styled("█ ]", accent_style().add_modifier(Modifier::BOLD)));
} else if label == "Theme:" || label == "Resync on Tab Change:" {
spans.push(Span::styled("< ", muted_style()));
spans.push(Span::styled(
value.to_string(),
if is_selected {
accent_style().add_modifier(Modifier::BOLD)
} else {
primary_style()
},
));
spans.push(Span::styled(" >", muted_style()));
} else {
spans.push(Span::styled("[ ", muted_style()));
spans.push(Span::styled(
value.to_string(),
if is_selected {
accent_style().add_modifier(Modifier::BOLD)
} else {
primary_style()
},
));
spans.push(Span::styled(" ]", muted_style()));
}
let line_style = if is_selected && !is_editing {
Style::default().bg(ratatui::style::Color::Rgb(60, 60, 60))
} else {
Style::default()
};
Line::from(spans).style(line_style)
};
let theme_val = repo_cfg.theme.clone().unwrap_or_else(|| "default".to_string());
let theme_line = build_line(0, "Theme:", &theme_val, false);
let page_size_val =
repo_cfg.page_size.map(|v| v.to_string()).unwrap_or_else(|| "default".to_string());
let page_size_line = build_line(
1,
"Page Size:",
if app.repo_settings_selected_index == 1 && app.repo_settings_editing {
&app.repo_settings_input
} else {
&page_size_val
},
app.repo_settings_selected_index == 1 && app.repo_settings_editing,
);
let max_commits_val =
repo_cfg.max_commits.map(|v| v.to_string()).unwrap_or_else(|| "default".to_string());
let max_commits_line = build_line(
2,
"Max Commits:",
if app.repo_settings_selected_index == 2 && app.repo_settings_editing {
&app.repo_settings_input
} else {
&max_commits_val
},
app.repo_settings_selected_index == 2 && app.repo_settings_editing,
);
let resync_val = match repo_cfg.resync_on_tab_change {
None => "default",
Some(true) => "yes",
Some(false) => "no",
};
let resync_line = build_line(3, "Resync on Tab Change:", resync_val, false);
let settings_lines = vec![theme_line, page_size_line, max_commits_line, resync_line];
f.render_widget(Paragraph::new(settings_lines), chunks[3]);
let helper_line = if app.repo_settings_editing {
Line::from(vec![
Span::styled(" [Digits] ", accent_style()),
Span::styled("Type ", muted_style()),
Span::styled(" [Enter] ", accent_style()),
Span::styled("Confirm ", muted_style()),
Span::styled(" [Esc] ", accent_style()),
Span::styled("Cancel", muted_style()),
])
} else {
Line::from(vec![
Span::styled(" [↑/↓/j/k] ", accent_style()),
Span::styled("Navigate ", muted_style()),
Span::styled(" [←/→/h/l/Space] ", accent_style()),
Span::styled("Change/Edit ", muted_style()),
Span::styled(" [Esc/q] ", accent_style()),
Span::styled("Close", muted_style()),
])
};
f.render_widget(Paragraph::new(helper_line).alignment(Alignment::Center), chunks[5]);
}
}