use crate::data_provider::history::{get_history_of_set, history_dir}; 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::dictionary::{DictionaryMessage, DictionaryState, app_data_dir}; use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState}; use crate::history::{HistoryMessage, HistoryState}; use crate::import::{ImportMessage, ImportState}; use crate::lang::Id; use crate::message_navigation; use crate::navigation::Page::*; use crate::navigation::RootMessage::{DataLoaded, Keyboard, UpdateData}; use crate::quiz::{QuizMessage, QuizState}; use crate::randomizer::{RandomizerMessage, RandomizerState}; use crate::repetition::{RepetitionMessage, RepetitionState}; use crate::repetition_settings::{RepetitionSettingsMessage, RepetitionSettingsState}; use crate::repetitions::{RepetitionsMessage, RepetitionsState}; use crate::selector::{SelectorMessage, SelectorState}; use crate::state_navigate_handle; use crate::state_update; use crate::sync::{SyncMessage, SyncState}; use crate::view_navigation; use crate::word::{WordMessage, WordState}; use crate::writing::{WritingMessage, WritingState}; use crate::{AppState, fill_state}; use chrono::NaiveDate; use hashbrown::HashMap; use iced::keyboard::Event; use iced::{Element, Task}; use reqwest::Error; use rusqlite::Connection; use std::sync::{Arc, Mutex}; 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(), } } } #[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>), None, UpdateData, } 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>, } impl ScreenState { pub fn boot() -> (ScreenState, Task) { println!("Booting"); create_db(); println!("Db created"); let state = ScreenState::default(); let reading_additional_task = Task::perform(Self::load_additional_data(), |data| data); let final_task; let state_guard = state.app_state.lock().unwrap(); if state_guard.sync_data.auto_web_fetch { let key = state_guard.sync_data.key.clone().unwrap(); final_task = Task::batch([ reading_additional_task, Task::perform(Self::load_web_backup(key), |result| { if let Ok(update) = result && update { UpdateData } else { RootMessage::None } }), ]); } else { final_task = reading_additional_task; } drop(state_guard); println!("Boot finished"); (state, final_task) } async fn load_additional_data() -> RootMessage { let directory = history_dir(); let mut map = HashMap::new(); for file in directory.read_dir().unwrap().flatten() { let mut vec = vec![]; let history_file_name = file.file_name().into_string().unwrap(); let id: Id = history_file_name[4..history_file_name.len() - 12] .parse::() .unwrap(); let 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); } 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?; println!("{} {}", local, web); if web > local { println!("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 { println!("Updating"); let time = Instant::now(); if let Keyboard(e) = message { return self.handle_keyboard(e); } if let DataLoaded(data) = message { self.app_state.lock().unwrap().activity = data; return Task::none(); } if let UpdateData = message { println!("Loading data"); let mut state = self.app_state.lock().unwrap(); if cfg!(windows) { state.connection = Connection::open_in_memory().unwrap(); } let path = app_data_dir(); let db_file = path.join("data.db"); let temp_db_file = path.join("data.db.tmp"); std::fs::rename(temp_db_file, &db_file).unwrap(); state.connection = default_connection(); fill_state(&mut state); return Task::none(); } 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 ); } println!("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> { println!("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 ); println!("View took {:?}", time.elapsed()); view } fn handle_keyboard(&mut self, message: Event) -> Task { let page = self.stack.last_mut().unwrap(); match page { Repetition(page) => page.press(&message), _ => 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().unwrap() { $( $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().unwrap() { 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 { $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().unwrap() { $( 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; }