use std::time::Instant;
use crate::app::{App, Focus};
use ratatui::{
Frame,
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::Paragraph,
};
#[derive(Debug, Clone)]
pub struct FlashMessage {
pub text: String,
pub expires_at: Instant,
}
impl FlashMessage {
#[allow(dead_code)] pub fn new(text: impl Into<String>, duration: std::time::Duration) -> Self {
Self { text: text.into(), expires_at: Instant::now() + duration }
}
pub fn is_active(&self) -> bool {
Instant::now() < self.expires_at
}
}
pub fn draw(f: &mut Frame, app: &App, flash: Option<&FlashMessage>, area: Rect) {
let p = &app.palette;
let focus_label = match app.focus {
Focus::Dashboard => "DASHBOARD",
Focus::FirstRun => "WELCOME",
Focus::Detail if app.copy_mode.active => "COPY",
Focus::Detail => "DETAIL",
Focus::RepoPicker => "REPOS",
Focus::Help => "HELP",
Focus::Confirm => "CONFIRM",
Focus::Composer => "COMPOSE",
Focus::ThemePicker => "THEME",
};
let fetch_indicator = if app.fetching {
Span::styled(" syncing... ", Style::default().fg(p.dim))
} else if let Some(pending) = &app.pending_mutation {
Span::styled(format!(" {}... ", pending.label()), Style::default().fg(p.warning))
} else if app.detail_refreshing.is_some() {
Span::styled(" refreshing... ", Style::default().fg(p.dim))
} else if let Some((ready, total, in_flight)) = app.commit_diff_cache_counts()
&& ready < total
&& in_flight > 0
{
Span::styled(format!(" warming diffs {ready}/{total}... "), Style::default().fg(p.warning))
} else {
Span::raw(" ")
};
let center_text =
flash.filter(|m| m.is_active()).map_or_else(String::new, |m| format!(" {} ", m.text));
let hints = match app.focus {
Focus::Dashboard => {
"j/k nav Enter detail i toggle A all/mine r refresh c theme p repos ? help q quit"
}
Focus::FirstRun => "Space toggle Enter confirm a add Esc skip",
Focus::Detail if app.copy_mode.active => {
"h/j/k/l move 0/$ line ends V select y yank Y yank line Esc exit"
}
Focus::Detail => {
"!@#$% sections C commits Up/Down files M merge S squash A comment R reply"
}
Focus::RepoPicker => "j/k nav a add d delete Enter select Esc close",
Focus::Help => "? / Esc / q close help",
Focus::Confirm => "[y] confirm [N] / Esc cancel",
Focus::Composer => "type markdown Enter newline Ctrl+S submit Esc cancel",
Focus::ThemePicker => "j/k move Enter apply Esc cancel",
};
let commit_scope_span: Option<Span<'static>> =
if let (Some(idx), Focus::Detail) = (app.selected_commit, app.focus) {
app.pr_detail.as_ref().and_then(|d| d.commits.get(idx)).map(|c| {
let glyph = if app.config.show_ascii_glyphs { "@" } else { "\u{25c8}" }; Span::styled(
format!(" {glyph} {} H\u{2192}HEAD ", c.short_sha),
Style::default().fg(p.warning),
)
})
} else {
None
};
let mut spans = vec![
Span::styled(
format!(" {focus_label} "),
Style::default().fg(p.on_accent_fg).bg(p.accent).add_modifier(Modifier::BOLD),
),
fetch_indicator,
Span::styled(center_text, Style::default().fg(p.status_bar_fg)),
Span::styled(format!(" {hints} "), Style::default().fg(p.dim)),
];
if let Some(scope_span) = commit_scope_span {
spans.push(scope_span);
}
let line = Line::from(spans);
let paragraph = Paragraph::new(line).style(Style::default().bg(p.status_bar_bg));
f.render_widget(paragraph, area);
}