use crate::app::{AppState, fill_state}; use crate::data::Id; use crate::data_provider::history::get_history_of_set; use crate::data_provider::sqlite::{create_db, default_connection}; use crate::data_provider::web_api::{ get_local_version, get_web_version, load_data, set_local_version, }; use crate::fs::*; use crate::navigation::Page::*; use crate::navigation::RootMessage::{ DataLoaded, Keyboard, Notification, PushNotification, UpdateData, }; use crate::notification::NotificationData::PermissionDenied; use crate::notification::{ NotificationData, NotificationManager, NotificationMessage, NotificationRequest, }; use crate::pages::dictionary::{DictionaryMessage, DictionaryState}; use crate::pages::dictionary_quiz::{DictionaryQuizMessage, DictionaryQuizState}; use crate::pages::history::{HistoryMessage, HistoryState}; use crate::pages::import::{ImportMessage, ImportState}; use crate::pages::quiz::{QuizMessage, QuizState}; use crate::pages::randomizer::{RandomizerMessage, RandomizerState}; use crate::pages::repetition::{RepetitionMessage, RepetitionState}; use crate::pages::repetition_settings::{RepetitionSettingsMessage, RepetitionSettingsState}; use crate::pages::repetitions::{RepetitionsMessage, RepetitionsState}; use crate::pages::selector::{SelectorMessage, SelectorState}; use crate::pages::sync::{SyncMessage, SyncState}; use crate::pages::word::{WordMessage, WordState}; use crate::pages::writing::{WritingMessage, WritingState}; use crate::state_navigate_handle; use crate::state_update; use crate::view_navigation; use crate::{message_navigation, push_error, push_error_task}; use chrono::NaiveDate; use hashbrown::HashMap; use iced::keyboard::Event; use iced::widget::stack; use iced::{Element, Task}; use iced_core::Length::Fill; use log::info; use parking_lot::Mutex; use rusqlite::Connection; use std::sync::Arc; use std::time::Instant; impl Default for ScreenState { fn default() -> Self { let mut state = AppState::new(); fill_state(&mut state); let state = Arc::new(Mutex::new(state)); ScreenState { stack: vec![Selector(SelectorState::new(state.clone()))], app_state: state.clone(), notification_manager: NotificationManager::new(), } } } #[derive(Clone)] pub enum RootMessage { Selector(SelectorMessage), Quiz(QuizMessage), Writing(WritingMessage), Dictionary(DictionaryMessage), DictionaryQuiz(DictionaryQuizMessage), Randomizer(RandomizerMessage), Repetitions(RepetitionsMessage), Repetition(RepetitionMessage), Word(WordMessage), Sync(SyncMessage), History(HistoryMessage), RepetitionSettings(RepetitionSettingsMessage), Import(ImportMessage), Keyboard(Event), DataLoaded(HashMap>), PushNotification(NotificationRequest), Notification(NotificationMessage), None, UpdateData, } #[allow(clippy::enum_variant_names)] #[allow(clippy::large_enum_variant)] pub enum Page { Selector(SelectorState), Quiz(QuizState), Writing(WritingState), Dictionary(DictionaryState), DictionaryQuiz(DictionaryQuizState), Randomizer(RandomizerState), Repetitions(RepetitionsState), Repetition(RepetitionState), Word(WordState), Sync(SyncState), History(HistoryState), RepetitionSettings(RepetitionSettingsState), Import(ImportState), PreviousPage, } pub struct ScreenState { stack: Vec, app_state: Arc>, notification_manager: NotificationManager, } impl ScreenState { pub fn boot() -> (ScreenState, Task) { info!("Booting"); create_db(); info!("Db created"); let state = ScreenState::default(); let reading_additional_task = Task::perform(Self::load_additional_data(), |data| { data.unwrap_or_else(|err| push_error!(err)) }); let mut final_task = reading_additional_task; let state_guard = state.app_state.lock(); if state_guard.sync_data.auto_web_fetch && let Some(key) = state_guard.sync_data.key.clone() { final_task = Task::batch([ final_task, Task::perform(Self::load_web_backup(key), |result| match result { Ok(update) => { if update { UpdateData } else { RootMessage::None } } Err(err) => push_error!(err), }), ]); } drop(state_guard); info!("Boot finished"); (state, final_task) } async fn load_additional_data() -> Result { let directory = history_dir()?; let mut map = HashMap::new(); for file in directory .read_dir() .map_err(|_| PermissionDenied)? .filter_map(Result::ok) { let mut vec = vec![]; let history_file_name = match file.file_name().into_string() { Err(_) => continue, Ok(v) => v, }; if let Ok(id) = history_file_name.split('_').collect::>()[1].parse::() && let Ok(history) = get_history_of_set(id) { let by_date = history.chunk_by(|x, x1| { x.timestamp.naive_local().date() == x1.timestamp.naive_local().date() }); for group in by_date { vec.push((group[0].timestamp.naive_local().date(), group.len() as u32)); } map.insert(id, vec); } else { continue; } } Ok(DataLoaded(map)) } async fn load_web_backup(string: String) -> Result { let local = get_local_version().await?; let web = get_web_version(string.as_str()).await?; info!("{} {}", local, web); if web > local { info!("Web version is newer than local version"); load_data(string, true).await?; set_local_version(web).await?; } else { return Ok(false); } Ok(true) } pub fn update(&mut self, message: RootMessage) -> Task { info!("Updating"); let time = Instant::now(); if let Keyboard(e) = message { return self.handle_keyboard(e); } if let DataLoaded(data) = message { self.app_state.lock().activity = data; return Task::none(); } if let UpdateData = message { info!("Loading data"); let mut state = self.app_state.lock(); if cfg!(windows) { state.connection = Connection::open_in_memory().unwrap(); } let db_file = db_path(); let temp_db_file = temp_db_path(); if std::fs::rename(temp_db_file, &db_file).is_err() { return push_error_task!(PermissionDenied); } state.connection = default_connection(); fill_state(&mut state); return Task::none(); } if let Notification(message) = message { return self.notification_manager.update(message); } if let PushNotification(notification) = &message { return self.notification_manager.push(notification.clone()); } let task: (Task, bool) = self.navigate_or_update(message); if task.1 { state_navigate_handle!( self.stack, Selector, Quiz, Writing, Dictionary, DictionaryQuiz, Randomizer, Repetitions, Repetition, Word, Sync, History, RepetitionSettings, Import ); } info!("Update took {:?}", time.elapsed()); task.0 } fn navigate_or_update(&mut self, message: RootMessage) -> (Task, bool) { state_update!( message, self.stack, Selector, Quiz, Writing, Dictionary, DictionaryQuiz, Randomizer, Repetitions, Repetition, Word, Sync, History, RepetitionSettings, Import ) } pub fn view(&self) -> Element<'_, RootMessage> { info!("View update"); let time = Instant::now(); let view = view_navigation!( self.stack, Quiz, Selector, Writing, Dictionary, DictionaryQuiz, Randomizer, Repetitions, Repetition, Word, Sync, History, RepetitionSettings, Import ); let notifications = self.notification_manager.draw().map(Notification); info!("View took {:?}", time.elapsed()); stack![view, notifications].width(Fill).height(Fill).into() } fn handle_keyboard(&mut self, message: Event) -> Task { if let Some(page) = self.stack.last_mut() { return match page { Repetition(page) => page.press(&message), _ => Task::none(), }; } Task::none() } } pub struct AppSettings { pub(crate) key: Option, pub(crate) auto_web_fetch: bool, } #[macro_export] macro_rules! view_navigation { ($stack:expr, $($e:ident), *) => { match &$stack.last().expect("Unexpected stack state") { $( $e(s) => s.view().map(RootMessage::$e), )* _ => iced::widget::space().into(), } } } #[macro_export] macro_rules! state_update { ($message:expr, $stack:expr, $($e:ident), *) => { match $message { $( RootMessage::$e(msg) => { if let $e(s) = $stack.last_mut().expect("Unexpected stack state") { message_navigation!(msg, $stack, s) } else { return (Task::::none(), false); } } )* _ => return (Task::::none(), false), } } } #[macro_export] macro_rules! message_navigation { ($msg:expr, $stack:expr, $state:expr) => { if let Some(new_page) = $state.navigate(&$msg) { if let Page::PreviousPage = new_page { if $stack.len() > 1 { $stack.pop(); } return (Task::::none(), true); } else { $stack.push(new_page); } return (Task::::none(), false); } else { return ($state.update($msg), false); } }; } #[macro_export] macro_rules! state_navigate_handle { ($stack:expr, $($e:ident), *) => { match $stack.last_mut().expect("Unexpected stack state") { $( Page::$e(page) => { page.navigated(); } )* _ => {}, } } } pub trait NavigatedPage { fn navigate(&mut self, message: &T) -> Option; fn navigated(&mut self); fn update(&mut self, message: T) -> Task; fn view(&self) -> Element<'_, T>; } pub trait KeyPressedPage { fn press(&mut self, message: &Event) -> Task; }