diff --git a/src/app/mod.rs b/src/app/mod.rs index 376b69b..1a57294 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,7 +1,7 @@ -use std::fs; +use std::{borrow::Cow, fs}; use iced::{ - Color, Length, Subscription, Task, application, event, + Alignment, Color, Length, Subscription, Task, application, event, keyboard::{self, Key, key}, theme, widget, }; @@ -20,12 +20,10 @@ enum Message { ChangePassword(String), ChangeSecurity(bool), OpenDb, + DbOpened(Result), FocusNext, FocusPrevious, - - Enter, - Pick, } #[derive(Default)] @@ -38,6 +36,9 @@ struct State { password: String, is_secure: bool, + + status: Cow<'static, str>, + error: Cow<'static, str>, } impl State { @@ -81,36 +82,27 @@ impl State { Message::ChangePassword(password) => self.password = password, Message::ChangeSecurity(bool) => self.is_secure = bool, - Message::OpenDb => { - match self.open_db() { - Err(e) => eprintln!("failed to open db '{}': {e}", self.db_path), - Ok(()) => { - #[allow(unused_must_use)] - db::set_last_opened(&self.db_path) - .inspect_err(|e| eprintln!("failed to set last-opened db: {e}")); + Message::OpenDb => return self.open_db(), + + Message::DbOpened(result) => { + match result { + Ok(db) => { + self.error = "".into(); + self.db = Some(db) } - }; + Err(e) => self.error = e.into(), + } + self.status = "".into(); } Message::FocusNext => return widget::operation::focus_next(), Message::FocusPrevious => return widget::operation::focus_previous(), - - Message::Enter => { - if self.db.is_none() && self.db_path_exists { - return Task::done(Message::OpenDb); - } - } - Message::Pick => { - if self.db.is_none() { - return Task::done(Message::PickDb); - } - } } Task::none() } fn view(&self) -> Element<'_> { - if let Some(db) = self.db.as_ref() { + let main = if let Some(db) = self.db.as_ref() { widget::scrollable(widget::row([ widget::column(db.iter_all_entries().map(|entry| { widget::text(entry.get_title().unwrap_or_default().to_string()).into() @@ -163,44 +155,72 @@ impl State { ) .center(Length::Fill) .into() + }; + + let mut bar = Vec::new(); + if !self.error.is_empty() { + bar.push( + widget::container(widget::text(&self.error).style(widget::text::danger)) + .align_x(Alignment::Start) + .width(Length::Fill) + .into(), + ); } + if !self.status.is_empty() { + bar.push( + widget::container(widget::text(&self.status)) + .align_x(Alignment::End) + .width(Length::Fill) + .into(), + ); + } + + widget::column([main, widget::row(bar).into()]).into() } fn subscription(&self) -> Subscription { - event::listen().filter_map(|event| match event { - iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => { - match key.as_ref() { - Key::Named(key::Named::Tab) => Some(if modifiers.shift() { - Message::FocusPrevious - } else { - Message::FocusNext - }), + event::listen() + .with((self.db.is_some(), self.db_path_exists)) + .filter_map(move |((db_loaded, db_path_exists), event)| match event { + iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => { + match key.as_ref() { + Key::Named(key::Named::Tab) => Some(if modifiers.shift() { + Message::FocusPrevious + } else { + Message::FocusNext + }), - Key::Named(key::Named::Enter) => Some(Message::Enter), - Key::Character("p" | "f" | "i") => Some(Message::Pick), + Key::Named(key::Named::Enter) if !db_loaded && db_path_exists => { + Some(Message::OpenDb) + } + Key::Character("p" | "f" | "i") if !db_loaded => Some(Message::PickDb), - // ignore unused keys - _ => None, + // ignore unused keys + _ => None, + } } - } - // ignore unused events - _ => None, - }) + // ignore unused events + _ => None, + }) } - fn open_db(&mut self) -> Result<(), String> { + fn open_db(&mut self) -> Task { debug_assert!(self.db_path_exists); - self.db = Some( - keepass::Database::open( - &mut fs::File::open(&self.db_path).map_err(|e| e.to_string())?, - keepass::DatabaseKey::new().with_password(&self.password), - ) - .map_err(|e| e.to_string())?, - ); + self.status = "loading...".into(); - Ok(()) + let (db_path, password) = (self.db_path.clone(), self.password.clone()); + Task::perform( + async move { + Ok(keepass::Database::open( + &mut fs::File::open(&db_path).map_err(|e| e.to_string())?, + keepass::DatabaseKey::new().with_password(&password), + ) + .map_err(|e| e.to_string())?) + }, + Message::DbOpened, + ) } }