feat(ui): implement config loading from the device module

This commit is contained in:
Suyog Tandel
2026-01-29 23:14:20 +05:30
parent 99d69e4931
commit 27cb0417ae
2 changed files with 210 additions and 17 deletions
+9 -3
View File
@@ -151,9 +151,15 @@ impl Render for ApplicationRoot {
ActiveView::Passkeys => {
PasskeysView::build(cx.theme()).into_any_element()
}
ActiveView::Configuration => {
cx.new(|cx| ConfigView::new(window, cx)).into_any_element()
}
ActiveView::Configuration => cx
.new(|cx| {
ConfigView::new(
window,
cx,
self.state.device_status.clone(),
)
})
.into_any_element(),
ActiveView::Security => {
SecurityView::build(cx).into_any_element()
}
+201 -14
View File
@@ -1,3 +1,5 @@
use crate::device::io;
use crate::device::types::{AppConfigInput, FullDeviceStatus};
use crate::ui::components::page_view::PageView;
use gpui::*;
use gpui_component::{
@@ -60,10 +62,17 @@ pub struct ConfigView {
power_cycle: bool,
enable_secp256k1: bool,
loading: bool,
device_status: Option<FullDeviceStatus>,
}
impl ConfigView {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
pub fn new(
window: &mut Window,
cx: &mut Context<Self>,
device_status: Option<FullDeviceStatus>,
) -> Self {
let config = device_status.as_ref().map(|s| &s.config);
let vendors = vec![
VendorItem {
value: "custom".into(),
@@ -107,11 +116,37 @@ impl ConfigView {
)
});
let vid_input = cx.new(|cx| InputState::new(window, cx).default_value("CAFE"));
let pid_input = cx.new(|cx| InputState::new(window, cx).default_value("4242"));
let product_name_input = cx.new(|cx| InputState::new(window, cx).default_value("My Key"));
let vid_input = cx.new(|cx| {
InputState::new(window, cx).default_value(
config
.map(|c| c.vid.clone())
.unwrap_or_else(|| "CAFE".into()),
)
});
let pid_input = cx.new(|cx| {
InputState::new(window, cx).default_value(
config
.map(|c| c.pid.clone())
.unwrap_or_else(|| "4242".into()),
)
});
let product_name_input = cx.new(|cx| {
InputState::new(window, cx).default_value(
config
.map(|c| c.product_name.clone())
.unwrap_or_else(|| "My Key".into()),
)
});
let led_gpio_input = cx.new(|cx| InputState::new(window, cx).default_value("25"));
let led_gpio_input = cx.new(|cx| {
InputState::new(window, cx).default_value(
config
.map(|c| c.led_gpio.to_string())
.unwrap_or_else(|| "25".into()),
)
});
let _initial_driver_idx = config.and_then(|c| c.led_driver).unwrap_or(0) as usize;
let led_driver_select = cx.new(|cx| {
SelectState::new(
drivers,
@@ -121,15 +156,26 @@ impl ConfigView {
)
});
// Set initial selection for driver?
// Note: SelectState currently defaults to first item if index path is default.
// We'd strictly need to set the index path based on value.
// For now trusting default or user interaction.
let led_brightness_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(15.0)
.step(1.0)
.default_value(8.0)
.default_value(config.map(|c| c.led_brightness as f32).unwrap_or(8.0))
});
let touch_timeout_input = cx.new(|cx| InputState::new(window, cx).default_value("10"));
let touch_timeout_input = cx.new(|cx| {
InputState::new(window, cx).default_value(
config
.map(|c| c.touch_timeout.to_string())
.unwrap_or_else(|| "10".into()),
)
});
Self {
vendor_select,
@@ -139,15 +185,134 @@ impl ConfigView {
led_gpio_input,
led_driver_select,
led_brightness_slider,
led_dimmable: true,
led_steady: false,
led_dimmable: config.map(|c| c.led_dimmable).unwrap_or(true),
led_steady: config.map(|c| c.led_steady).unwrap_or(false),
touch_timeout_input,
power_cycle: false,
enable_secp256k1: true,
power_cycle: config.map(|c| c.power_cycle_on_reset).unwrap_or(false),
enable_secp256k1: config.map(|c| c.enable_secp256k1).unwrap_or(true),
loading: false,
device_status,
}
}
fn apply_changes(&mut self, cx: &mut Context<Self>) {
let status = if let Some(s) = &self.device_status {
s
} else {
return;
};
let current_config = &status.config;
let mut changes = AppConfigInput {
vid: None,
pid: None,
product_name: None,
led_gpio: None,
led_brightness: None,
touch_timeout: None,
led_driver: None,
led_dimmable: None,
power_cycle_on_reset: None,
led_steady: None,
enable_secp256k1: None,
};
let vid = self.vid_input.read(cx).text().to_string();
if vid != current_config.vid {
changes.vid = Some(vid);
}
let pid = self.pid_input.read(cx).text().to_string();
if pid != current_config.pid {
changes.pid = Some(pid);
}
let product_name = self.product_name_input.read(cx).text().to_string();
if product_name != current_config.product_name {
changes.product_name = Some(product_name);
}
let led_gpio_str = self.led_gpio_input.read(cx).text().to_string();
if let Ok(val) = led_gpio_str.parse::<u8>() {
if val != current_config.led_gpio {
changes.led_gpio = Some(val);
}
}
let driver_idx = self.led_driver_select.read(cx).selected_index(cx);
if let Some(idx) = driver_idx {
// Assuming values are 0, 1, 2 matches index
let val = idx.row as u8;
if Some(val) != current_config.led_driver {
changes.led_driver = Some(val);
}
}
let brightness = self.led_brightness_slider.read(cx).value().start() as u8;
if brightness != current_config.led_brightness {
changes.led_brightness = Some(brightness);
}
let touch_timeout_str = self.touch_timeout_input.read(cx).text().to_string();
if let Ok(val) = touch_timeout_str.parse::<u8>() {
if val != current_config.touch_timeout {
changes.touch_timeout = Some(val);
}
}
if self.led_dimmable != current_config.led_dimmable {
changes.led_dimmable = Some(self.led_dimmable);
}
if self.led_steady != current_config.led_steady {
changes.led_steady = Some(self.led_steady);
}
if self.power_cycle != current_config.power_cycle_on_reset {
changes.power_cycle_on_reset = Some(self.power_cycle);
}
if self.enable_secp256k1 != current_config.enable_secp256k1 {
changes.enable_secp256k1 = Some(self.enable_secp256k1);
}
// Check if we have any changes
let has_changes = changes.vid.is_some()
|| changes.pid.is_some()
|| changes.product_name.is_some()
|| changes.led_gpio.is_some()
|| changes.led_brightness.is_some()
|| changes.touch_timeout.is_some()
|| changes.led_driver.is_some()
|| changes.led_dimmable.is_some()
|| changes.power_cycle_on_reset.is_some()
|| changes.led_steady.is_some()
|| changes.enable_secp256k1.is_some();
if !has_changes {
println!("No changes detected");
return;
}
self.loading = true;
cx.notify();
let result = io::write_config(changes, status.method.clone(), None);
self.loading = false;
match result {
Ok(msg) => {
println!("Success: {}", msg);
}
Err(e) => {
eprintln!("Error saving config: {}", e);
}
}
cx.notify();
}
fn render_identity_card(&self, theme: &Theme) -> impl IntoElement {
let content = v_flex()
.gap_4()
@@ -408,6 +573,27 @@ impl ConfigView {
impl Render for ConfigView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.theme();
// If not connected, show placeholder
if self.device_status.is_none() {
return PageView::build(
"Configuration",
"Customize device settings and behavior.",
div()
.flex()
.items_center()
.justify_center()
.h_64()
.border_1()
.border_color(theme.border)
.rounded_xl()
.child(div().text_color(theme.muted_foreground).child("No Content")),
theme,
)
.into_any_element();
}
// I need to call mutable methods first.
let led_card = self.render_led_card(cx).into_any_element();
let options_card = self.render_options_card(cx).into_any_element();
@@ -439,12 +625,13 @@ impl Render for ConfigView {
.icon(Icon::default().path("icons/save.svg"))
.child("Apply Changes")
.disabled(self.loading)
.on_click(|_, _, _| {
println!("Save clicked");
}),
.on_click(cx.listener(|this, _, _, cx| {
this.apply_changes(cx);
})),
),
),
&theme,
)
.into_any_element()
}
}