Skip to main content

aranet_cli/tui/ui/
colors.rs

1//! Color helper functions for the TUI.
2//!
3//! This module provides color coding for various sensor readings and status indicators.
4//!
5//! # CO2 Level Color Coding
6//!
7//! CO2 levels are color-coded based on air quality guidelines:
8//!
9//! | Range (ppm)  | Color  | Meaning                                    |
10//! |--------------|--------|--------------------------------------------|
11//! | 0-800        | Green  | Good - Normal outdoor/indoor air quality   |
12//! | 801-1000     | Yellow | Moderate - Acceptable, some sensitivity    |
13//! | 1001-1500    | Orange | Elevated - Consider improving ventilation  |
14//! | 1501+        | Red    | High - Poor ventilation, take action       |
15//!
16//! These thresholds are based on indoor air quality standards and the Aranet4
17//! device's built-in status indicators.
18
19use aranet_types::Status;
20use ratatui::style::Color;
21
22use super::super::app::ConnectionStatus;
23
24/// Returns a color based on CO2 concentration level.
25///
26/// # Arguments
27///
28/// * `ppm` - CO2 concentration in parts per million
29///
30/// # Returns
31///
32/// A [`Color`] representing the air quality:
33/// - Green: 0-800 ppm (good)
34/// - Yellow: 801-1000 ppm (moderate)
35/// - Orange (RGB 255,165,0): 1001-1500 ppm (elevated)
36/// - Red: 1501+ ppm (high)
37#[must_use]
38pub fn co2_color(ppm: u16) -> Color {
39    match ppm {
40        0..=800 => Color::Green,
41        801..=1000 => Color::Yellow,
42        1001..=1500 => Color::Rgb(255, 165, 0),
43        _ => Color::Red,
44    }
45}
46
47/// Returns a color based on radon concentration level.
48///
49/// Radon levels are color-coded based on EPA guidelines:
50///
51/// | Range (Bq/m³) | Color  | Meaning                                    |
52/// |---------------|--------|--------------------------------------------|
53/// | 0-100         | Green  | Good - Low risk                            |
54/// | 101-150       | Yellow | Moderate - Consider mitigation             |
55/// | 151-300       | Orange | Elevated - Mitigation recommended          |
56/// | 301+          | Red    | High - Take action                         |
57///
58/// Note: EPA action level is 4 pCi/L ≈ 148 Bq/m³
59#[must_use]
60pub fn radon_color(bq_m3: u32) -> Color {
61    match bq_m3 {
62        0..=100 => Color::Green,
63        101..=150 => Color::Yellow,
64        151..=300 => Color::Rgb(255, 165, 0),
65        _ => Color::Red,
66    }
67}
68
69/// Returns a color based on the sensor status indicator.
70///
71/// # Arguments
72///
73/// * `status` - The status from the sensor reading
74///
75/// # Returns
76///
77/// A [`Color`] matching the status:
78/// - Green status → Green color
79/// - Yellow status → Yellow color
80/// - Red status → Red color
81/// - Error status → DarkGray color
82// Kept for future use: displaying sensor status indicators in the TUI
83#[allow(dead_code)]
84#[must_use]
85pub fn status_color(status: &Status) -> Color {
86    match status {
87        Status::Green => Color::Green,
88        Status::Yellow => Color::Yellow,
89        Status::Red => Color::Red,
90        Status::Error => Color::DarkGray,
91        // Handle future non_exhaustive variants
92        _ => Color::DarkGray,
93    }
94}
95
96/// Returns a color based on battery percentage.
97///
98/// # Arguments
99///
100/// * `percent` - Battery level as a percentage (0-100)
101///
102/// # Returns
103///
104/// A [`Color`] representing the battery level:
105/// - Red: 0-20% (low, needs charging)
106/// - Yellow: 21-50% (moderate)
107/// - Green: 51-100% (good)
108#[must_use]
109pub fn battery_color(percent: u8) -> Color {
110    match percent {
111        0..=20 => Color::Red,
112        21..=50 => Color::Yellow,
113        _ => Color::Green,
114    }
115}
116
117/// Returns a color based on the connection status.
118///
119/// # Arguments
120///
121/// * `status` - The current connection status
122///
123/// # Returns
124///
125/// A [`Color`] representing the connection state:
126/// - DarkGray: Disconnected
127/// - Yellow: Connecting (in progress)
128/// - Green: Connected
129/// - Red: Error
130// Kept for future use: displaying connection status in the TUI header/footer
131#[allow(dead_code)]
132#[must_use]
133pub fn connection_status_color(status: &ConnectionStatus) -> Color {
134    match status {
135        ConnectionStatus::Disconnected => Color::DarkGray,
136        ConnectionStatus::Connecting => Color::Yellow,
137        ConnectionStatus::Connected => Color::Green,
138        ConnectionStatus::Error(_) => Color::Red,
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_co2_color_good() {
148        assert_eq!(co2_color(0), Color::Green);
149        assert_eq!(co2_color(400), Color::Green);
150        assert_eq!(co2_color(800), Color::Green);
151    }
152
153    #[test]
154    fn test_co2_color_moderate() {
155        assert_eq!(co2_color(801), Color::Yellow);
156        assert_eq!(co2_color(900), Color::Yellow);
157        assert_eq!(co2_color(1000), Color::Yellow);
158    }
159
160    #[test]
161    fn test_co2_color_elevated() {
162        assert_eq!(co2_color(1001), Color::Rgb(255, 165, 0));
163        assert_eq!(co2_color(1250), Color::Rgb(255, 165, 0));
164        assert_eq!(co2_color(1500), Color::Rgb(255, 165, 0));
165    }
166
167    #[test]
168    fn test_co2_color_high() {
169        assert_eq!(co2_color(1501), Color::Red);
170        assert_eq!(co2_color(2000), Color::Red);
171        assert_eq!(co2_color(5000), Color::Red);
172    }
173
174    #[test]
175    fn test_battery_color() {
176        assert_eq!(battery_color(0), Color::Red);
177        assert_eq!(battery_color(20), Color::Red);
178        assert_eq!(battery_color(21), Color::Yellow);
179        assert_eq!(battery_color(50), Color::Yellow);
180        assert_eq!(battery_color(51), Color::Green);
181        assert_eq!(battery_color(100), Color::Green);
182    }
183
184    #[test]
185    fn test_status_color() {
186        assert_eq!(status_color(&Status::Green), Color::Green);
187        assert_eq!(status_color(&Status::Yellow), Color::Yellow);
188        assert_eq!(status_color(&Status::Red), Color::Red);
189        assert_eq!(status_color(&Status::Error), Color::DarkGray);
190    }
191
192    #[test]
193    fn test_connection_status_color() {
194        assert_eq!(
195            connection_status_color(&ConnectionStatus::Disconnected),
196            Color::DarkGray
197        );
198        assert_eq!(
199            connection_status_color(&ConnectionStatus::Connecting),
200            Color::Yellow
201        );
202        assert_eq!(
203            connection_status_color(&ConnectionStatus::Connected),
204            Color::Green
205        );
206        assert_eq!(
207            connection_status_color(&ConnectionStatus::Error("test".to_string())),
208            Color::Red
209        );
210    }
211
212    #[test]
213    fn test_radon_color_good() {
214        assert_eq!(radon_color(0), Color::Green);
215        assert_eq!(radon_color(50), Color::Green);
216        assert_eq!(radon_color(100), Color::Green);
217    }
218
219    #[test]
220    fn test_radon_color_moderate() {
221        assert_eq!(radon_color(101), Color::Yellow);
222        assert_eq!(radon_color(125), Color::Yellow);
223        assert_eq!(radon_color(150), Color::Yellow);
224    }
225
226    #[test]
227    fn test_radon_color_elevated() {
228        assert_eq!(radon_color(151), Color::Rgb(255, 165, 0));
229        assert_eq!(radon_color(200), Color::Rgb(255, 165, 0));
230        assert_eq!(radon_color(300), Color::Rgb(255, 165, 0));
231    }
232
233    #[test]
234    fn test_radon_color_high() {
235        assert_eq!(radon_color(301), Color::Red);
236        assert_eq!(radon_color(500), Color::Red);
237        assert_eq!(radon_color(1000), Color::Red);
238    }
239}