git_same/setup/screens/
requirements.rs1use 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), Constraint::Min(8), Constraint::Length(3), ])
16 .split(area);
17
18 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 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 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;