Skip to main content

git_same/setup/screens/
requirements.rs

1//! Step 1: System requirements check.
2
3use crate::setup::state::SetupState;
4use ratatui::layout::{Constraint, Layout, Rect};
5use ratatui::style::{Color, Modifier, Style};
6use ratatui::text::{Line, Span};
7use ratatui::widgets::Paragraph;
8use ratatui::Frame;
9
10pub fn render(state: &SetupState, frame: &mut Frame, area: Rect) {
11    let chunks = Layout::vertical([
12        Constraint::Length(2), // Title
13        Constraint::Min(8),    // Check results or spinner
14        Constraint::Length(3), // Config status + action hint
15    ])
16    .split(area);
17
18    // Title
19    let title_text = if state.is_first_setup {
20        "Welcome to Git-Same"
21    } else {
22        "System Requirements"
23    };
24    let title = Paragraph::new(title_text).style(
25        Style::default()
26            .fg(Color::Cyan)
27            .add_modifier(Modifier::BOLD),
28    );
29    frame.render_widget(title, chunks[0]);
30
31    // Check list or spinner
32    if state.checks_loading {
33        let spinner_frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
34        let frame_idx = (state.tick_count as usize / 2) % spinner_frames.len();
35        let spinner = spinner_frames[frame_idx];
36        let loading = Paragraph::new(Line::from(vec![
37            Span::styled(
38                format!("  {} ", spinner),
39                Style::default().fg(Color::Yellow),
40            ),
41            Span::styled(
42                "Checking requirements...",
43                Style::default().fg(Color::DarkGray),
44            ),
45        ]));
46        frame.render_widget(loading, chunks[1]);
47    } else if state.check_results.is_empty() {
48        let placeholder = Paragraph::new(Line::from(Span::styled(
49            "  Preparing checks...",
50            Style::default().fg(Color::DarkGray),
51        )));
52        frame.render_widget(placeholder, chunks[1]);
53    } else {
54        let lines: Vec<Line> = state
55            .check_results
56            .iter()
57            .map(|check| {
58                let (icon, icon_color) = if check.passed {
59                    ("  ✓ ", Color::Rgb(21, 128, 61))
60                } else if check.critical {
61                    ("  ✗ ", Color::Red)
62                } else {
63                    ("  ! ", Color::Yellow)
64                };
65                let msg_color = if check.passed {
66                    Color::DarkGray
67                } else if check.critical {
68                    Color::Red
69                } else {
70                    Color::Yellow
71                };
72                Line::from(vec![
73                    Span::styled(
74                        icon,
75                        Style::default().fg(icon_color).add_modifier(Modifier::BOLD),
76                    ),
77                    Span::styled(
78                        format!("{:<18}", &check.name),
79                        Style::default().fg(Color::White),
80                    ),
81                    Span::styled(" — ", Style::default().fg(Color::DarkGray)),
82                    Span::styled(&check.message, Style::default().fg(msg_color)),
83                ])
84            })
85            .collect();
86        frame.render_widget(Paragraph::new(lines), chunks[1]);
87    }
88
89    // Config status + action hint
90    let mut status_lines: Vec<Line> = Vec::new();
91
92    if let Some(ref path) = state.config_path_display {
93        let (label, color) = if state.config_was_created {
94            ("  Config created at ", Color::Rgb(21, 128, 61))
95        } else {
96            ("  Config found at ", Color::DarkGray)
97        };
98        status_lines.push(Line::from(vec![
99            Span::styled(label, Style::default().fg(color)),
100            Span::styled(path, Style::default().fg(Color::Cyan)),
101        ]));
102    }
103
104    if !state.check_results.is_empty() && !state.checks_loading {
105        let has_critical_fail = state.check_results.iter().any(|r| r.critical && !r.passed);
106        if has_critical_fail {
107            status_lines.push(Line::from(Span::styled(
108                "  Fix critical requirements above to continue.",
109                Style::default().fg(Color::Red),
110            )));
111        } else {
112            status_lines.push(Line::from(vec![
113                Span::styled(
114                    "  All requirements met. Press ",
115                    Style::default().fg(Color::DarkGray),
116                ),
117                Span::styled(
118                    "[Enter]",
119                    Style::default()
120                        .fg(Color::Rgb(37, 99, 235))
121                        .add_modifier(Modifier::BOLD),
122                ),
123                Span::styled(" to continue.", Style::default().fg(Color::DarkGray)),
124            ]));
125        }
126    }
127
128    frame.render_widget(Paragraph::new(status_lines), chunks[2]);
129}
130
131#[cfg(test)]
132#[path = "requirements_tests.rs"]
133mod tests;