feat: starting page
This commit is contained in:
parent
9000399c7c
commit
e4ddc86caf
6 changed files with 674 additions and 14 deletions
162
src/app/mod.rs
162
src/app/mod.rs
|
|
@ -1,25 +1,175 @@
|
|||
use iced::{Color, Task, application, theme, widget};
|
||||
use std::fs;
|
||||
|
||||
use iced::{
|
||||
Color, Length, Subscription, Task, application, event,
|
||||
keyboard::{self, Key, key},
|
||||
theme, widget,
|
||||
};
|
||||
use rfd::{AsyncFileDialog, FileHandle};
|
||||
|
||||
use crate::db;
|
||||
|
||||
type Element<'a> = iced::Element<'a, Message, iced::Theme, iced::Renderer>;
|
||||
|
||||
enum Message {}
|
||||
#[derive(Clone)]
|
||||
enum Message {
|
||||
ChangeDbPath(String),
|
||||
PickDb,
|
||||
PickedDb(Option<FileHandle>),
|
||||
|
||||
struct State {}
|
||||
ChangePassword(String),
|
||||
ChangeSecurity(bool),
|
||||
OpenDb,
|
||||
|
||||
FocusNext,
|
||||
FocusPrevious,
|
||||
|
||||
Enter,
|
||||
Pick,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
db_path: String,
|
||||
db_path_exists: bool,
|
||||
|
||||
db_loaded: bool,
|
||||
|
||||
password: String,
|
||||
|
||||
is_secure: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
(
|
||||
Self {
|
||||
is_secure: true,
|
||||
..Default::default()
|
||||
},
|
||||
if let Ok(db_path) = db::get_last_opened()
|
||||
.inspect_err(|e| eprintln!("failed to get last-opened db: {e}"))
|
||||
{
|
||||
Task::done(Message::ChangeDbPath(db_path))
|
||||
} else {
|
||||
Task::none()
|
||||
},
|
||||
)
|
||||
}
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::ChangeDbPath(db_path) => {
|
||||
self.db_path_exists = fs::exists(&db_path)
|
||||
.inspect_err(|e| eprintln!("failed to check '{db_path}': {e}"))
|
||||
.is_ok_and(|exists| exists);
|
||||
self.db_path = db_path;
|
||||
}
|
||||
Message::PickDb => {
|
||||
return Task::perform(
|
||||
AsyncFileDialog::new()
|
||||
.add_filter("kdbx", &["kdbx"])
|
||||
.pick_file(),
|
||||
Message::PickedDb,
|
||||
);
|
||||
}
|
||||
Message::PickedDb(Some(file)) => {
|
||||
return Task::done(Message::ChangeDbPath(
|
||||
file.path().to_string_lossy().to_string(),
|
||||
));
|
||||
}
|
||||
Message::PickedDb(None) => eprintln!("no path received from picker"),
|
||||
|
||||
Message::ChangePassword(password) => self.password = password,
|
||||
Message::ChangeSecurity(bool) => self.is_secure = bool,
|
||||
Message::OpenDb => todo!(),
|
||||
|
||||
Message::FocusNext => return widget::operation::focus_next(),
|
||||
Message::FocusPrevious => return widget::operation::focus_previous(),
|
||||
|
||||
Message::Enter => {
|
||||
if !self.db_loaded && self.db_path_exists {
|
||||
return Task::done(Message::OpenDb);
|
||||
}
|
||||
}
|
||||
Message::Pick => {
|
||||
if !self.db_loaded {
|
||||
return Task::done(Message::PickDb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
fn view(&self) -> Element<'_> {
|
||||
widget::text("hiiiiiii").into()
|
||||
if !self.db_loaded {
|
||||
widget::container(
|
||||
widget::column([
|
||||
widget::row([
|
||||
widget::text_input("database path", &self.db_path)
|
||||
.on_input(Message::ChangeDbPath)
|
||||
.into(),
|
||||
widget::button("pick file").on_press(Message::PickDb).into(),
|
||||
])
|
||||
.into(),
|
||||
widget::row([
|
||||
widget::text_input("password", &self.password)
|
||||
.on_input(Message::ChangePassword)
|
||||
.secure(self.is_secure)
|
||||
.into(),
|
||||
widget::checkbox(self.is_secure)
|
||||
.label("security")
|
||||
.on_toggle(Message::ChangeSecurity)
|
||||
.into(),
|
||||
])
|
||||
.spacing(10)
|
||||
.into(),
|
||||
widget::container(widget::button("open database").on_press_maybe(
|
||||
if self.db_path_exists {
|
||||
Some(Message::OpenDb)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
))
|
||||
.center_x(Length::Fill)
|
||||
.into(),
|
||||
])
|
||||
.spacing(20)
|
||||
.padding(20),
|
||||
)
|
||||
.center(Length::Fill)
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
}
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
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
|
||||
}),
|
||||
|
||||
Key::Named(key::Named::Enter) => Some(Message::Enter),
|
||||
Key::Character("p" | "f" | "i") => Some(Message::Pick),
|
||||
|
||||
// ignore unused keys
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// ignore unused events
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), iced::Error> {
|
||||
application(State::new, State::update, State::view)
|
||||
.subscription(State::subscription)
|
||||
.theme(theme::Theme::custom(
|
||||
"high-contrast-dark",
|
||||
theme::Palette {
|
||||
|
|
|
|||
26
src/db.rs
Normal file
26
src/db.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::{fs, io};
|
||||
|
||||
pub fn get_last_opened() -> io::Result<String> {
|
||||
Ok(fs::read_to_string(
|
||||
dirs::data_dir()
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"unable to find data_dir",
|
||||
))?
|
||||
.join("keepice.txt"),
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn set_last_opened(path: impl AsRef<str>) -> io::Result<()> {
|
||||
fs::write(
|
||||
dirs::data_dir()
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"unable to find data_dir",
|
||||
))?
|
||||
.join("keepice.txt"),
|
||||
path.as_ref(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod app;
|
||||
mod db;
|
||||
|
||||
fn main() -> Result<(), impl std::error::Error> {
|
||||
app::run()
|
||||
|
|
|
|||
Loading…
Reference in a new issue