diff --git a/src/app.rs b/src/app.rs index f12bc67..fd9fead 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,148 @@ -pub struct App; +use crate::book::Book; +use crate::persistence; +use crate::theme::{self, Settings}; +use eframe::egui; +use std::path::PathBuf; + +pub struct App { + pub state: AppState, + settings: Settings, + settings_dir: std::path::PathBuf, +} + +pub struct AppState { + pub book: Option, + pub current_section: usize, + pub current_page: usize, + pub sidebar_open: bool, + pub file_path: Option, + pub error_message: Option, +} impl App { - pub fn new(_cc: &eframe::CreationContext) -> Self { - Self + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + let settings_dir = persistence::settings_dir(); + let settings = persistence::load_settings(&settings_dir).unwrap_or_default(); + cc.egui_ctx.set_style(theme::create_style(&settings.theme)); + Self { + state: AppState { + book: None, + current_section: 0, + current_page: 0, + sidebar_open: false, + file_path: None, + error_message: None, + }, + settings, + settings_dir, + } + } + + pub fn open_file(&mut self, path: PathBuf) { + match crate::book::load_epub(&path) { + Ok(book) => { + let path_str = path.to_string_lossy().to_string(); + let pos = self.settings.reading_positions.get(&path_str).copied(); + let mut recent = Vec::new(); + recent.push(path_str.clone()); + for f in &self.settings.recent_files { + if *f != path_str { + recent.push(f.clone()); + if recent.len() >= 10 { + break; + } + } + } + self.settings.recent_files = recent; + self.state.book = Some(book); + self.state.current_section = pos.map(|p| p.section).unwrap_or(0); + self.state.current_page = pos.map(|p| p.page).unwrap_or(0); + self.state.sidebar_open = false; + self.state.file_path = Some(path); + self.state.error_message = None; + self.save_settings(); + } + Err(e) => { + self.state.error_message = Some(e); + } + } + } + + fn save_settings(&self) { + let _ = persistence::save_settings(&self.settings_dir, &self.settings); + } + + fn save_reading_position(&mut self) { + if let Some(ref path) = self.state.file_path { + let path_str = path.to_string_lossy().to_string(); + self.settings.reading_positions.insert( + path_str, + theme::ReadingPosition { + section: self.state.current_section, + page: self.state.current_page, + }, + ); + } + } + + fn welcome_view(&mut self, ctx: &egui::Context) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add_space(150.0); + ui.heading("ePub Reader"); + ui.add_space(20.0); + if ui + .add(egui::Button::new("πŸ“‚ 打开 ePub ζ–‡δ»Ά").min_size(egui::vec2(200.0, 40.0))) + .clicked() + { + if let Some(path) = rfd::FileDialog::new() + .add_filter("ePub", &["epub"]) + .pick_file() + { + self.open_file(path); + } + } + ui.add_space(30.0); + if !self.settings.recent_files.is_empty() { + ui.label("ζœ€θΏ‘ι˜…θ―»:"); + ui.separator(); + let mut to_open: Option = None; + let mut to_remove: Option = None; + for (i, path) in self.settings.recent_files.iter().enumerate() { + let name = std::path::Path::new(path) + .file_name() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| path.clone()); + if ui.button(&name).clicked() { + let p = std::path::PathBuf::from(path); + if p.exists() { + to_open = Some(p); + } else { + to_remove = Some(i); + } + } + } + if let Some(path) = to_open { + self.open_file(path); + } + if let Some(i) = to_remove { + self.settings.recent_files.remove(i); + self.save_settings(); + } + } + if let Some(ref msg) = self.state.error_message { + ui.add_space(20.0); + ui.colored_label(egui::Color32::RED, msg); + } + }); + }); } } impl eframe::App for App { - fn update(&mut self, _ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {} + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + if self.state.book.is_none() { + self.welcome_view(ctx); + } + } }