Files
jap_learn/src/navigation.rs
T

330 lines
9.6 KiB
Rust

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::{app_data_dir, DictionaryMessage, DictionaryState};
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
use crate::history::{HistoryMessage, HistoryState};
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 iced::keyboard::Event;
use iced::{Element, Task};
use reqwest::Error;
use std::collections::HashMap;
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),
Keyboard(Event),
DataLoaded(HashMap<u32, Vec<(NaiveDate, u32)>>),
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),
PreviousPage,
}
pub struct ScreenState {
stack: Vec<Page>,
app_state: Arc<Mutex<AppState>>,
}
impl ScreenState {
pub fn boot() -> (ScreenState, Task<RootMessage>) {
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() {
if let Ok(file) = file {
let mut vec = vec![];
let history_file_name = file.file_name().into_string().unwrap();
let id = history_file_name[4..history_file_name.len() - 12]
.parse::<u32>()
.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<bool, Error> {
let local = get_local_version().await;
let web = get_web_version(string.as_str()).await?;
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<RootMessage> {
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();
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<RootMessage>, 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
);
}
println!("Update took {:?}", time.elapsed());
task.0
}
fn navigate_or_update(&mut self, message: RootMessage) -> (Task<RootMessage>, bool) {
state_update!(
message,
self.stack,
Selector,
Quiz,
Writing,
Dictionary,
DictionaryQuiz,
Randomizer,
Repetitions,
Repetition,
Word,
Sync,
History,
RepetitionSettings
)
}
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
);
println!("View took {:?}", time.elapsed());
view
}
fn handle_keyboard(&mut self, message: Event) -> Task<RootMessage> {
let page = self.stack.last_mut().unwrap();
match page {
Repetition(page) => page.press(&message),
_ => Task::none(),
}
}
}
pub struct AppSettings {
pub(crate) key: Option<String>,
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::<RootMessage>::none(), false);
}
}
)*
_ => return (Task::<RootMessage>::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::<RootMessage>::none(), true);
} else {
$stack.push(new_page);
}
return (Task::<RootMessage>::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<T> {
fn navigate(&self, message: &T) -> Option<Page>;
fn navigated(&mut self);
fn update(&mut self, message: T) -> Task<RootMessage>;
fn view(&self) -> Element<'_, T>;
}
pub trait KeyPressedPage {
fn press(&mut self, message: &Event) -> Task<RootMessage>;
}