diff --git a/src/logging.rs b/src/logging.rs index 9602637..3d8cdab 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -58,20 +58,28 @@ pub fn logger_init() { ))) .build(); - let (app_level, root_level) = if cfg!(debug_assertions) { - (LevelFilter::Trace, LevelFilter::Debug) + let app_level = if cfg!(debug_assertions) { + LevelFilter::Trace } else { - (LevelFilter::Info, LevelFilter::Error) + LevelFilter::Info }; let config = log4rs::Config::builder() .appender(Appender::builder().build("stdout", Box::new(stdout))) .appender(Appender::builder().build("logfile", Box::new(logfile))) - .logger(Logger::builder().build("picoforge", app_level)) + .logger( + Logger::builder() + .appenders(["stdout", "logfile"]) + .additive(false) + .build("picoforge", app_level), + ) + .logger(Logger::builder().build("gpui", LevelFilter::Error)) + .logger(Logger::builder().build("gpui_component", LevelFilter::Error)) + .logger(Logger::builder().build("blade_graphics", LevelFilter::Error)) .build( Root::builder() .appenders(vec!["logfile", "stdout"]) - .build(root_level), + .build(LevelFilter::Error), ) .unwrap(); diff --git a/src/ui/components/sidebar.rs b/src/ui/components/sidebar.rs index 35caaca..f57e098 100644 --- a/src/ui/components/sidebar.rs +++ b/src/ui/components/sidebar.rs @@ -1,6 +1,6 @@ use crate::device::types::DeviceMethod; use crate::ui::components::button::PFIconButton; -use crate::ui::types::{ActiveView, GlobalDeviceState}; +use crate::ui::types::{ActiveView, DeviceConnectionState}; use gpui::*; use gpui_component::{ ActiveTheme, Icon, IconName, Side, @@ -18,7 +18,7 @@ pub struct AppSidebar { active_view: ActiveView, width: Pixels, collapsed: bool, - state: GlobalDeviceState, + state: DeviceConnectionState, on_select: SelectHandler, on_refresh: RefreshHandler, } @@ -28,7 +28,7 @@ impl AppSidebar { active_view: ActiveView, width: Pixels, collapsed: bool, - state: GlobalDeviceState, + state: DeviceConnectionState, ) -> Self { Self { active_view, @@ -191,7 +191,7 @@ impl AppSidebar { .w_full(), ) .child(div().w(px(8.)).h(px(8.)).rounded_full().bg( - if let Some(status) = &state.device_status { + if let Some(status) = &state.status { if status.method == DeviceMethod::Fido { rgb(0xf59e0b) } else { @@ -220,7 +220,7 @@ impl AppSidebar { ) .child({ let (text, color_bg, color_text) = - if let Some(status) = &state.device_status { + if let Some(status) = &state.status { if status.method == DeviceMethod::Fido { ("Online - Fido", rgb(0xf59e0b), rgb(0xffffff)) } else { diff --git a/src/ui/rootview.rs b/src/ui/rootview.rs index 498ad1a..90d1f93 100644 --- a/src/ui/rootview.rs +++ b/src/ui/rootview.rs @@ -1,11 +1,10 @@ use crate::device::io; use crate::ui::components::sidebar::AppSidebar; -use crate::ui::types::{ActiveView, GlobalDeviceState}; +use crate::ui::types::{ActiveView, DeviceConnectionState, LayoutState, ViewCache}; use crate::ui::views::{ about::AboutView, config::ConfigView, home::HomeView, passkeys::PasskeysEvent, passkeys::PasskeysView, security::SecurityView, }; - use gpui::prelude::*; use gpui::*; use gpui_component::Root; @@ -16,31 +15,21 @@ use gpui_component::{ gpui::actions!(picoforge, [ToggleSidebar]); pub struct ApplicationRoot { - active_view: ActiveView, - is_sidebar_collapsed: bool, - sidebar_toggle_hovered: bool, - state: GlobalDeviceState, - device_loading: bool, - // Umm, why did my past self do this? This does not belong here. - sidebar_width: Pixels, - config_view: Option>, - passkeys_view: Option>, - focus_handle: FocusHandle, + pub device: DeviceConnectionState, + pub layout: LayoutState, + pub views: ViewCache, + pub focus_handle: FocusHandle, } impl ApplicationRoot { pub fn new(cx: &mut Context) -> Self { let mut this = Self { - active_view: ActiveView::Home, - is_sidebar_collapsed: false, - sidebar_toggle_hovered: false, - state: GlobalDeviceState::new(), - device_loading: false, - sidebar_width: px(255.), - config_view: None, - passkeys_view: None, + device: DeviceConnectionState::new(), + layout: LayoutState::new(), + views: ViewCache::new(), focus_handle: cx.focus_handle(), }; + this.refresh_device_status(None, cx); this } @@ -50,56 +39,60 @@ impl ApplicationRoot { } fn refresh_device_status(&mut self, window: Option<&mut Window>, cx: &mut Context) { - if self.device_loading { + if self.device.loading { return; } - - self.device_loading = true; - self.state.error = None; + self.device.loading = true; + self.device.error = None; cx.notify(); match io::read_device_details() { Ok(status) => { - self.state.device_status = Some(status.clone()); - self.state.error = None; + let device_changed = self + .device + .status + .as_ref() + .map(|s| s.info.serial != status.info.serial) + .unwrap_or(true); + + self.device.status = Some(status); + self.device.error = None; + + if device_changed { + self.views.passkeys = None; + } match io::get_fido_info() { Ok(fido) => { - self.state.fido_info = Some(fido); + self.device.fido_info = Some(fido); } Err(e) => { log::error!("FIDO Info fetch failed: {}", e); - self.state.fido_info = None; + self.device.fido_info = None; } } - if let Some(config_view) = &self.config_view + if let Some(config_view) = &self.views.config && let Some(window) = window { + let device = self.device.clone(); config_view.update(cx, |view, cx| { - view.update_device_status(Some(status.clone()), window, cx); - }); - } - - if let Some(passkeys_view) = &self.passkeys_view { - let fido = self.state.fido_info.clone(); - passkeys_view.update(cx, |view, cx| { - view.update_device_status(Some(status.clone()), fido, cx); + view.sync_from_device(&device, window, cx); }); } } Err(e) => { - self.state.device_status = None; - self.state.error = Some(format!("{}", e)); - self.state.fido_info = None; + self.device.status = None; + self.device.error = Some(format!("{}", e)); + self.device.fido_info = None; } } - self.device_loading = false; + self.device.loading = false; cx.notify(); } pub fn toggle_sidebar(&mut self, cx: &mut Context) { - self.is_sidebar_collapsed = !self.is_sidebar_collapsed; + self.layout.is_sidebar_collapsed = !self.layout.is_sidebar_collapsed; cx.notify(); } } @@ -108,7 +101,7 @@ impl Render for ApplicationRoot { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let window_width = window.bounds().size.width; let is_window_wide = window_width > px(800.0); - let is_sidebar_collapsed = self.is_sidebar_collapsed || !is_window_wide; + let is_sidebar_collapsed = self.layout.is_sidebar_collapsed || !is_window_wide; let target_width = if is_sidebar_collapsed { px(48.) @@ -116,11 +109,12 @@ impl Render for ApplicationRoot { px(255.) }; - if (self.sidebar_width - target_width).abs() > px(0.1) { - self.sidebar_width = self.sidebar_width + (target_width - self.sidebar_width) * 0.2; + if (self.layout.sidebar_width - target_width).abs() > px(0.1) { + self.layout.sidebar_width = + self.layout.sidebar_width + (target_width - self.layout.sidebar_width) * 0.2; window.request_animation_frame(); } else { - self.sidebar_width = target_width; + self.layout.sidebar_width = target_width; } let dialog_layer = Root::render_dialog_layer(window, cx); @@ -146,21 +140,15 @@ impl Render for ApplicationRoot { .overflow_y_scrollbar() .flex_grow() .bg(cx.theme().background) - .child(match self.active_view { + .child(match self.layout.active_view { ActiveView::Home => { - HomeView::build(&self.state, cx.theme(), window.bounds().size.width) + HomeView::build(&self.device, cx.theme(), window.bounds().size.width) .into_any_element() } ActiveView::Passkeys => { - let view = self.passkeys_view.get_or_insert_with(|| { - let view = cx.new(|cx| { - PasskeysView::new( - window, - cx, - self.state.device_status.clone(), - self.state.fido_info.clone(), - ) - }); + let view = self.views.passkeys.get_or_insert_with(|| { + let root = cx.entity().downgrade(); + let view = cx.new(|cx| PasskeysView::new(window, cx, root)); cx.subscribe_in( &view, window, @@ -176,23 +164,26 @@ impl Render for ApplicationRoot { view.clone().into_any_element() } ActiveView::Configuration => { - let view = self.config_view.get_or_insert_with(|| { - cx.new(|cx| ConfigView::new(window, cx, self.state.device_status.clone())) - }); - view.clone().into_any_element() + if self.views.config.is_none() { + let root = cx.entity().downgrade(); + let device = self.device.clone(); + self.views.config = + Some(cx.new(|cx| ConfigView::new(window, cx, root, device))); + } + self.views.config.clone().unwrap().into_any_element() } ActiveView::Security => SecurityView::build(cx).into_any_element(), ActiveView::About => AboutView::build(cx.theme()).into_any_element(), }); let sidebar = AppSidebar::new( - self.active_view, - self.sidebar_width, + self.layout.active_view, + self.layout.sidebar_width, is_sidebar_collapsed, - self.state.clone(), + self.device.clone(), ) .on_select(|this: &mut Self, view, _, _| { - this.active_view = view; + this.layout.active_view = view; }) .on_refresh(|this, window, cx| { this.refresh_device_status(Some(window), cx); @@ -203,8 +194,8 @@ impl Render for ApplicationRoot { let sidebar_bg = cx.theme().sidebar; let border_color = cx.theme().sidebar_border; let sidebar_fg = cx.theme().sidebar_foreground; - let is_toggle_visible = !is_sidebar_collapsed || self.sidebar_toggle_hovered; - let sidebar_width = self.sidebar_width; + let is_toggle_visible = !is_sidebar_collapsed || self.layout.sidebar_toggle_hovered; + let sidebar_width = self.layout.sidebar_width; let toggle_icon = if is_sidebar_collapsed { "icons/chevron-right.svg" } else { @@ -226,7 +217,7 @@ impl Render for ApplicationRoot { .items_center() .justify_center() .on_hover(cx.listener(|this, hovered, _, cx| { - this.sidebar_toggle_hovered = *hovered; + this.layout.sidebar_toggle_hovered = *hovered; cx.notify(); })) .child( @@ -249,7 +240,7 @@ impl Render for ApplicationRoot { .build(window, cx) }) .on_click(cx.listener(|this, _, _, _| { - this.is_sidebar_collapsed = !this.is_sidebar_collapsed; + this.layout.is_sidebar_collapsed = !this.layout.is_sidebar_collapsed; })) .child(Icon::default().path(toggle_icon).text_color(sidebar_fg)), ); diff --git a/src/ui/types.rs b/src/ui/types.rs index f69df55..962e6f1 100644 --- a/src/ui/types.rs +++ b/src/ui/types.rs @@ -1,6 +1,8 @@ -use gpui::SharedString; - -use crate::device::types::{FidoDeviceInfo, FullDeviceStatus}; +use crate::{ + device::types::{FidoDeviceInfo, FullDeviceStatus}, + ui::views::{config::ConfigView, passkeys::PasskeysView}, +}; +use gpui::{Entity, Pixels, SharedString, px}; #[derive(Clone, Copy, PartialEq, Debug)] pub enum ActiveView { @@ -12,18 +14,53 @@ pub enum ActiveView { } #[derive(Clone, Debug, PartialEq)] -pub struct GlobalDeviceState { - pub device_status: Option, +pub struct DeviceConnectionState { + pub status: Option, pub fido_info: Option, pub error: Option, + pub loading: bool, } -impl GlobalDeviceState { +impl DeviceConnectionState { pub fn new() -> Self { Self { - device_status: None, + status: None, fido_info: None, error: None, + loading: false, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LayoutState { + pub active_view: ActiveView, + pub is_sidebar_collapsed: bool, + pub sidebar_toggle_hovered: bool, + pub sidebar_width: Pixels, +} + +impl LayoutState { + pub fn new() -> Self { + Self { + active_view: ActiveView::Home, + is_sidebar_collapsed: false, + sidebar_toggle_hovered: false, + sidebar_width: px(255.), + } + } +} + +pub struct ViewCache { + pub passkeys: Option>, + pub config: Option>, +} + +impl ViewCache { + pub fn new() -> Self { + Self { + passkeys: None, + config: None, } } } diff --git a/src/ui/views/config.rs b/src/ui/views/config.rs index 18b617c..7723566 100644 --- a/src/ui/views/config.rs +++ b/src/ui/views/config.rs @@ -1,12 +1,13 @@ use crate::device::io; -use crate::device::types::{AppConfigInput, FullDeviceStatus}; +use crate::device::types::AppConfigInput; use crate::ui::components::{ card::Card, dialog, dialog::{PinPromptContent, StatusContent}, page_view::PageView, }; -use crate::ui::types::{LedDriverType, UsbIdentityPreset}; +use crate::ui::rootview::ApplicationRoot; +use crate::ui::types::{DeviceConnectionState, LedDriverType, UsbIdentityPreset}; use gpui::*; use gpui_component::button::{ButtonCustomVariant, ButtonVariants}; use gpui_component::{ @@ -61,6 +62,7 @@ enum StatusDialogHandle { } pub struct ConfigView { + root: WeakEntity, vendor_select: Entity>>, vid_input: Entity, pid_input: Entity, @@ -74,7 +76,6 @@ pub struct ConfigView { power_cycle: bool, enable_secp256k1: bool, loading: bool, - device_status: Option, is_custom_vendor: bool, _task: Option>, } @@ -83,9 +84,10 @@ impl ConfigView { pub fn new( window: &mut Window, cx: &mut Context, - device_status: Option, + root: WeakEntity, + device: DeviceConnectionState, ) -> Self { - let config = device_status.as_ref().map(|s| &s.config); + let config = device.status.as_ref().map(|s| &s.config); let vendors: Vec = UsbIdentityPreset::all() .iter() @@ -197,6 +199,7 @@ impl ConfigView { cx.new(|cx| InputState::new(window, cx).default_value(current_touch_timeout.clone())); Self { + root, vendor_select, vid_input, pid_input, @@ -210,7 +213,6 @@ impl ConfigView { 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: device_status.clone(), is_custom_vendor, _task: None, } @@ -224,6 +226,14 @@ impl ConfigView { dialog_handle: StatusDialogHandle, cx: &mut Context, ) { + let expected_serial = self.root.upgrade().and_then(|r| { + r.read(cx) + .device + .status + .as_ref() + .map(|s| s.info.serial.clone()) + }); + self.loading = true; cx.notify(); @@ -254,19 +264,28 @@ impl ConfigView { log::info!("Success: {}", msg); if let Some(Ok(new_status)) = new_status_result { - log::info!( - "Refreshed device status. LED Steady: {}", - new_status.config.led_steady - ); + let serial_matches = expected_serial.as_deref() + == Some(new_status.info.serial.as_str()); - let config = &new_status.config; + if serial_matches { + log::info!( + "Refreshed device status. LED Steady: {}", + new_status.config.led_steady + ); - this.led_dimmable = config.led_dimmable; - this.led_steady = config.led_steady; - this.power_cycle = config.power_cycle_on_reset; - this.enable_secp256k1 = config.enable_secp256k1; + let config = &new_status.config; + this.led_dimmable = config.led_dimmable; + this.led_steady = config.led_steady; + this.power_cycle = config.power_cycle_on_reset; + this.enable_secp256k1 = config.enable_secp256k1; - this.device_status = Some(new_status); + let _ = this.root.update(cx, |root, cx| { + root.device.status = Some(new_status); + cx.notify(); + }); + } else { + log::warn!("Device changed during config write, discarding stale status"); + } } match &dialog_handle { @@ -349,11 +368,11 @@ impl ConfigView { } fn apply_changes(&mut self, window: &mut Window, cx: &mut Context) { - let status = if let Some(s) = &self.device_status { - s - } else { + let Some(root) = self.root.upgrade() else { return; }; + let device = root.read(cx).device.clone(); + let Some(status) = &device.status else { return }; let current_config = &status.config; let mut changes = AppConfigInput { @@ -397,7 +416,8 @@ impl ConfigView { && let Some(driver) = LedDriverType::all().get(idx.row) { let val = driver.value(); - if Some(val) != current_config.led_driver { + let current_val = current_config.led_driver.unwrap_or(1); + if val != current_val { changes.led_driver = Some(val); } } @@ -460,17 +480,13 @@ impl ConfigView { } } - pub(crate) fn update_device_status( + pub fn sync_from_device( &mut self, - status: Option, + device: &DeviceConnectionState, window: &mut Window, cx: &mut Context, ) { - if self.device_status == status { - return; - } - self.device_status = status.clone(); - let config = status.as_ref().map(|s| &s.config); + let config = device.status.as_ref().map(|s| &s.config); let vid = config .map(|c| c.vid.clone()) @@ -511,6 +527,19 @@ impl ConfigView { self.led_brightness_slider .update(cx, |slider, cx| slider.set_value(brightness, window, cx)); + let new_driver_val = config.and_then(|c| c.led_driver).unwrap_or(1); + let new_driver_idx = LedDriverType::all() + .iter() + .position(|d| d.value() == new_driver_val) + .unwrap_or(0); + self.led_driver_select.update(cx, |select, cx| { + select.set_selected_index( + Some(gpui_component::IndexPath::default().row(new_driver_idx)), + window, + cx, + ); + }); + cx.notify(); } @@ -727,7 +756,13 @@ impl ConfigView { impl Render for ConfigView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); - if self.device_status.is_none() { + let has_device = self + .root + .upgrade() + .map(|r| r.read(cx).device.status.is_some()) + .unwrap_or(false); + + if !has_device { return PageView::build( "Configuration", "Customize device settings and behavior.", diff --git a/src/ui/views/home.rs b/src/ui/views/home.rs index 513a87a..67e5b7e 100644 --- a/src/ui/views/home.rs +++ b/src/ui/views/home.rs @@ -1,6 +1,6 @@ use crate::device::types::DeviceMethod; use crate::ui::components::{card::Card, page_view::PageView, tag::Tag}; -use crate::ui::types::GlobalDeviceState; +use crate::ui::types::DeviceConnectionState; use gpui::prelude::FluentBuilder; use gpui::*; use gpui_component::StyledExt; @@ -10,11 +10,11 @@ pub struct HomeView; impl HomeView { pub fn build( - state: &GlobalDeviceState, + state: &DeviceConnectionState, theme: &Theme, window_width: Pixels, ) -> impl IntoElement { - let connected = state.device_status.is_some(); + let connected = state.status.is_some(); let is_wide = window_width > px(1100.0); let columns = if is_wide { 2 } else { 1 }; @@ -82,8 +82,8 @@ impl HomeView { ) } - fn render_device_info(state: &GlobalDeviceState, theme: &Theme) -> impl IntoElement { - let status = state.device_status.as_ref().unwrap(); + fn render_device_info(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement { + let status = state.status.as_ref().unwrap(); let info = &status.info; let config = &status.config; @@ -159,7 +159,7 @@ impl HomeView { ) } - fn render_fido_info(state: &GlobalDeviceState, theme: &Theme) -> impl IntoElement { + fn render_fido_info(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement { Card::new() .title("FIDO2 Information") .icon(Icon::default().path("icons/shield.svg")) @@ -277,8 +277,8 @@ impl HomeView { }) } - fn render_led_config(state: &GlobalDeviceState, theme: &Theme) -> impl IntoElement { - let status = state.device_status.as_ref().unwrap(); + fn render_led_config(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement { + let status = state.status.as_ref().unwrap(); let config = &status.config; Card::new() .title("LED Configuration") @@ -365,8 +365,8 @@ impl HomeView { }) } - fn render_security_status(state: &GlobalDeviceState, theme: &Theme) -> impl IntoElement { - let status = state.device_status.as_ref().unwrap(); + fn render_security_status(state: &DeviceConnectionState, theme: &Theme) -> impl IntoElement { + let status = state.status.as_ref().unwrap(); Card::new() .title("Security Status") .icon(Icon::default().path("icons/shield-check.svg")) diff --git a/src/ui/views/passkeys.rs b/src/ui/views/passkeys.rs index fc5b08c..50daa78 100644 --- a/src/ui/views/passkeys.rs +++ b/src/ui/views/passkeys.rs @@ -1,5 +1,5 @@ use crate::device::io; -use crate::device::types::{FidoDeviceInfo, FullDeviceStatus, StoredCredential}; +use crate::device::types::StoredCredential; use crate::ui::components::{ button::{PFButton, PFIconButton}, card::Card, @@ -7,6 +7,8 @@ use crate::ui::components::{ dialog::{ChangePinContent, ConfirmContent, PinPromptContent, SetPinContent, StatusContent}, page_view::PageView, }; +use crate::ui::rootview::ApplicationRoot; +use crate::ui::types::DeviceConnectionState; use gpui::*; use gpui_component::button::{Button, ButtonVariant, ButtonVariants}; use gpui_component::{ @@ -30,13 +32,11 @@ impl Render for SliderLabel { } pub struct PasskeysView { - device_status: Option, - fido_info: Option, + root: WeakEntity, credentials: Vec, unlocked: bool, cached_pin: Option, loading: bool, - _task: Option>, } @@ -50,12 +50,10 @@ impl PasskeysView { pub fn new( _window: &mut Window, _cx: &mut Context, - device_status: Option, - fido_info: Option, + root: WeakEntity, ) -> Self { Self { - device_status, - fido_info, + root, credentials: Vec::new(), unlocked: false, cached_pin: None, @@ -64,20 +62,6 @@ impl PasskeysView { } } - pub fn update_device_status( - &mut self, - status: Option, - fido_info: Option, - cx: &mut Context, - ) { - if self.device_status == status && self.fido_info == fido_info { - return; - } - self.device_status = status; - self.fido_info = fido_info; - cx.notify(); - } - fn unlock_storage( &mut self, pin: String, @@ -283,7 +267,10 @@ impl PasskeysView { Ok(msg) => { log::info!("PIN configured: {}", msg); if let Ok(info) = io::get_fido_info() { - this.fido_info = Some(info); + let _ = this.root.update(cx, |root, cx| { + root.device.fido_info = Some(info); + cx.notify(); + }); } let _ = dialog_handle.update(cx, |d, cx| { d.set_success("PIN configured successfully.".to_string(), cx); @@ -303,9 +290,15 @@ impl PasskeysView { fn open_min_pin_length_dialog(&mut self, window: &mut Window, cx: &mut Context) { let current_min = self - .fido_info - .as_ref() - .map(|f| f.min_pin_length) + .root + .upgrade() + .and_then(|r| { + r.read(cx) + .device + .fido_info + .as_ref() + .map(|f| f.min_pin_length) + }) .unwrap_or(4); let slider = cx.new(|_| { @@ -468,7 +461,10 @@ impl PasskeysView { Ok(msg) => { log::info!("PIN changed: {}", msg); if let Ok(info) = io::get_fido_info() { - this.fido_info = Some(info); + let _ = this.root.update(cx, |root, cx| { + root.device.fido_info = Some(info); + cx.notify(); + }); } let _ = dialog_handle.update(cx, |d, cx| { d.set_success("PIN changed successfully.".to_string(), cx); @@ -533,7 +529,10 @@ impl PasskeysView { Ok(_) => { log::info!("Minimum length and PIN updated successfully."); if let Ok(info) = io::get_fido_info() { - this.fido_info = Some(info); + let _ = this.root.update(cx, |root, cx| { + root.device.fido_info = Some(info); + cx.notify(); + }); } let _ = status_handle.update(cx, |s, cx| { s.set_success("Minimum length and PIN updated.".to_string(), cx); @@ -556,7 +555,10 @@ impl PasskeysView { this.loading = false; log::info!("Minimum PIN length updated to {}.", min_len); if let Ok(info) = io::get_fido_info() { - this.fido_info = Some(info); + let _ = this.root.update(cx, |root, cx| { + root.device.fido_info = Some(info); + cx.notify(); + }); } let _ = status_handle.update(cx, |s, cx| { s.set_success(format!("Minimum length updated to {}.", min_len), cx); @@ -613,8 +615,11 @@ impl PasskeysView { } fn render_pin_status_row(&self, cx: &mut Context) -> impl IntoElement { - let pin_set = self - .fido_info + let fido_info = self + .root + .upgrade() + .and_then(|r| r.read(cx).device.fido_info.clone()); + let pin_set = fido_info .as_ref() .and_then(|f| f.options.get("clientPin").copied()) .unwrap_or(false); @@ -660,13 +665,12 @@ impl PasskeysView { } fn render_min_pin_length_row(&self, cx: &mut Context) -> impl IntoElement { - let min_len = self - .fido_info - .as_ref() - .map(|f| f.min_pin_length) - .unwrap_or(4); - let pin_set = self - .fido_info + let fido_info = self + .root + .upgrade() + .and_then(|r| r.read(cx).device.fido_info.clone()); + let min_len = fido_info.as_ref().map(|f| f.min_pin_length).unwrap_or(4); + let pin_set = fido_info .as_ref() .and_then(|f| f.options.get("clientPin").copied()) .unwrap_or(false); @@ -1121,7 +1125,14 @@ impl PasskeysView { impl Render for PasskeysView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let device_connected = self.device_status.is_some(); + let device = self + .root + .upgrade() + .map(|r| r.read(cx).device.clone()) + .unwrap_or_else(DeviceConnectionState::new); + + let device_connected = device.status.is_some(); + if !device_connected { let theme = cx.theme(); return PageView::build( @@ -1133,12 +1144,12 @@ impl Render for PasskeysView { .into_any_element(); } - let has_fido = self - .device_status + let has_fido = device + .status .as_ref() .map(|s| s.method == crate::device::types::DeviceMethod::Fido) .unwrap_or(false) - || self.fido_info.is_some(); + || device.fido_info.is_some(); if !has_fido { let theme = cx.theme();