diff --git a/src/ui/rootview.rs b/src/ui/rootview.rs index 8e5e7eb..01adb41 100644 --- a/src/ui/rootview.rs +++ b/src/ui/rootview.rs @@ -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() } diff --git a/src/ui/views/config.rs b/src/ui/views/config.rs index 24b052c..e69b48e 100644 --- a/src/ui/views/config.rs +++ b/src/ui/views/config.rs @@ -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, } impl ConfigView { - pub fn new(window: &mut Window, cx: &mut Context) -> Self { + pub fn new( + window: &mut Window, + cx: &mut Context, + device_status: Option, + ) -> 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) { + 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::() { + 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::() { + 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) -> 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() } }