feat: starting page

This commit is contained in:
electria 2026-08-25 19:43:14 -07:00
commit e4ddc86caf
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
6 changed files with 674 additions and 14 deletions

View file

@ -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
View 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(())
}

View file

@ -1,4 +1,5 @@
mod app;
mod db;
fn main() -> Result<(), impl std::error::Error> {
app::run()