1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Device detail panel rendering.
//!
//! This module contains the device detail panel rendering logic,
//! showing device header, connection controls, and sensor readings.
use aranet_core::messages::Command;
use eframe::egui::{self, RichText};
use crate::gui::app::AranetApp;
use crate::gui::components;
use crate::gui::readings;
use crate::gui::types::{ConnectionState, DeviceState};
impl AranetApp {
/// Render the device detail panel.
pub(crate) fn render_device_panel(&self, ui: &mut egui::Ui, device: &DeviceState, idx: usize) {
// Device header
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.label(
RichText::new(device.display_name())
.size(self.theme.typography.heading)
.strong()
.color(self.theme.text_primary),
);
ui.horizontal(|ui| {
if let Some(device_type) = device.device_type {
components::status_badge(
ui,
&self.theme,
&format!("{:?}", device_type),
self.theme.info,
);
}
if let Some(rssi) = device.rssi {
let signal_color = if rssi > -60 {
self.theme.success
} else if rssi > -75 {
self.theme.warning
} else {
self.theme.danger
};
ui.add_space(self.theme.spacing.sm);
ui.label(
RichText::new(format!("{} dBm", rssi))
.size(self.theme.typography.caption)
.color(signal_color),
);
}
// Uptime badge for connected devices
if let Some(uptime) = device.uptime() {
ui.add_space(self.theme.spacing.sm);
ui.label(
RichText::new(format!("⏱ {}", uptime))
.size(self.theme.typography.caption)
.color(self.theme.text_muted),
);
}
// Status badges for warnings
if let Some(ref reading) = device.reading {
// Low battery badge
if reading.battery < 20 {
ui.add_space(self.theme.spacing.sm);
let battery_color = if reading.battery < 10 {
self.theme.danger
} else {
self.theme.warning
};
components::status_badge(
ui,
&self.theme,
&format!("{}% battery", reading.battery),
battery_color,
);
}
// Stale reading badge (age > 2x interval means stale)
let is_stale = reading.interval > 0 && reading.age > reading.interval * 2;
if is_stale {
ui.add_space(self.theme.spacing.sm);
components::status_badge(
ui,
&self.theme,
"stale reading",
self.theme.caution,
);
}
}
});
});
ui.with_layout(
egui::Layout::right_to_left(egui::Align::TOP),
|ui| match &device.connection {
ConnectionState::Disconnected | ConnectionState::Error(_) => {
let btn = egui::Button::new(
RichText::new("Connect")
.size(self.theme.typography.body)
.color(self.theme.text_on_accent),
)
.fill(self.theme.accent);
if ui.add(btn).clicked() {
self.send_command(Command::Connect {
device_id: device.id.clone(),
});
}
}
ConnectionState::Connecting => {
components::loading_indicator(ui, &self.theme, Some("Connecting..."));
}
ConnectionState::Reconnecting { attempt, .. } => {
let msg = format!("Reconnecting (attempt {})...", attempt);
components::loading_indicator(ui, &self.theme, Some(&msg));
}
ConnectionState::Connected => {
if ui
.add(egui::Button::new(
RichText::new("Refresh").size(self.theme.typography.body),
))
.on_hover_text("Cmd+R")
.clicked()
{
self.send_command(Command::RefreshReading {
device_id: device.id.clone(),
});
}
ui.add_space(self.theme.spacing.sm);
if ui
.add(egui::Button::new(
RichText::new("Disconnect")
.size(self.theme.typography.body)
.color(self.theme.danger),
))
.clicked()
{
self.send_command(Command::Disconnect {
device_id: device.id.clone(),
});
}
}
},
);
});
ui.add_space(self.theme.spacing.lg);
ui.separator();
ui.add_space(self.theme.spacing.lg);
// Readings content
if device.reading.is_some() {
readings::render_readings(
ui,
&self.theme,
device,
&self.gui_config.temperature_unit,
&self.gui_config.pressure_unit,
);
} else if device.connection == ConnectionState::Connected {
components::loading_indicator(ui, &self.theme, Some("Waiting for readings..."));
} else {
components::empty_state(
ui,
&self.theme,
"No Readings",
"Connect to the device to view sensor readings",
);
}
let _ = idx;
}
}