fix(ui): migrate views to read device state from ApplicationRoot

This commit is contained in:
Suyog Tandel
2026-03-06 00:38:45 +05:30
parent e7a1cab8ac
commit 30c926e049
4 changed files with 139 additions and 98 deletions
+23 -22
View File
@@ -42,16 +42,26 @@ impl ApplicationRoot {
if self.device.loading {
return;
}
self.device.loading = true;
self.device.error = None;
cx.notify();
match io::read_device_details() {
Ok(status) => {
self.device.status = Some(status.clone());
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.device.fido_info = Some(fido);
@@ -65,15 +75,9 @@ impl ApplicationRoot {
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.views.passkeys {
let fido = self.device.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);
});
}
}
@@ -143,14 +147,8 @@ impl Render for ApplicationRoot {
}
ActiveView::Passkeys => {
let view = self.views.passkeys.get_or_insert_with(|| {
let view = cx.new(|cx| {
PasskeysView::new(
window,
cx,
self.device.status.clone(),
self.device.fido_info.clone(),
)
});
let root = cx.entity().downgrade();
let view = cx.new(|cx| PasskeysView::new(window, cx, root));
cx.subscribe_in(
&view,
window,
@@ -166,10 +164,13 @@ impl Render for ApplicationRoot {
view.clone().into_any_element()
}
ActiveView::Configuration => {
let view = self.views.config.get_or_insert_with(|| {
cx.new(|cx| ConfigView::new(window, cx, self.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(),
-6
View File
@@ -32,12 +32,6 @@ impl DeviceConnectionState {
}
}
pub enum DeviceStateEvent {
Changed,
}
impl gpui::EventEmitter<DeviceStateEvent> for DeviceConnectionState {}
#[derive(Clone, Debug, PartialEq)]
pub struct LayoutState {
pub active_view: ActiveView,
+63 -28
View File
@@ -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<ApplicationRoot>,
vendor_select: Entity<SelectState<Vec<VendorSelectOption>>>,
vid_input: Entity<InputState>,
pid_input: Entity<InputState>,
@@ -74,7 +76,6 @@ pub struct ConfigView {
power_cycle: bool,
enable_secp256k1: bool,
loading: bool,
device_status: Option<FullDeviceStatus>,
is_custom_vendor: bool,
_task: Option<Task<()>>,
}
@@ -83,9 +84,10 @@ impl ConfigView {
pub fn new(
window: &mut Window,
cx: &mut Context<Self>,
device_status: Option<FullDeviceStatus>,
root: WeakEntity<ApplicationRoot>,
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<VendorSelectOption> = 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<Self>,
) {
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<Self>) {
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<FullDeviceStatus>,
device: &DeviceConnectionState,
window: &mut Window,
cx: &mut Context<Self>,
) {
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<Self>) -> 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.",
+53 -42
View File
@@ -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<FullDeviceStatus>,
fido_info: Option<FidoDeviceInfo>,
root: WeakEntity<ApplicationRoot>,
credentials: Vec<StoredCredential>,
unlocked: bool,
cached_pin: Option<String>,
loading: bool,
_task: Option<Task<()>>,
}
@@ -50,12 +50,10 @@ impl PasskeysView {
pub fn new(
_window: &mut Window,
_cx: &mut Context<Self>,
device_status: Option<FullDeviceStatus>,
fido_info: Option<FidoDeviceInfo>,
root: WeakEntity<ApplicationRoot>,
) -> 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<FullDeviceStatus>,
fido_info: Option<FidoDeviceInfo>,
cx: &mut Context<Self>,
) {
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<Self>) {
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<Self>) -> 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<Self>) -> 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<Self>) -> 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();