From 5a926b141903155d14b08c5986435eb53d3244b7 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sun, 23 Aug 2026 21:49:01 +0300 Subject: [PATCH] More notifications and move start code to another file --- src/app.rs | 109 ++++++++++++++++++++++++++++++++++ src/data_provider/import.rs | 98 ++++++++++++++++++------------- src/data_provider/voice.rs | 16 ++--- src/dictionary.rs | 4 +- src/history.rs | 4 +- src/import.rs | 45 +++++++------- src/lang.rs | 2 +- src/main.rs | 114 ++---------------------------------- src/navigation.rs | 2 +- src/notification.rs | 16 +++++ src/quiz.rs | 4 +- src/repetition.rs | 4 +- src/repetition_settings.rs | 3 +- src/repetitions.rs | 4 +- src/selector.rs | 5 +- src/sync.rs | 4 +- src/word.rs | 4 +- 17 files changed, 240 insertions(+), 198 deletions(-) create mode 100644 src/app.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..d749e21 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,109 @@ +use chrono::NaiveDate; +use hashbrown::HashMap; +use iced::{keyboard, window, Subscription}; +use iced_core::{Font, Size, Theme}; +use iced_core::window::Position; +use iced_core::window::settings::PlatformSpecific; +use rusqlite::Connection; +use crate::data_provider::card_sets::load_sets; +use crate::data_provider::settings::get_setting; +use crate::data_provider::sqlite::default_connection; +use crate::data_provider::words::{load_word_groups, load_words}; +use crate::lang::{DeckSettings, Id, WordData, WordGroup}; +use crate::navigation::{AppSettings, RootMessage, ScreenState}; +use crate::navigation::RootMessage::Keyboard; +pub const USER_FONT: Font = Font::with_name("Noto Sans JP"); + +pub fn start() -> iced::Result { + println!("Welcome to JapLearn"); + iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) + .window(window_settings()) + .subscription(subscription) + .title("JapLearn") + .settings(iced::Settings { + default_text_size: iced::Pixels(18.0), + ..iced::Settings::default() + }) + .font(include_bytes!("../noto.ttf")) + .default_font(USER_FONT) + .theme(Theme::GruvboxDark) + .run() +} + +#[allow(unused_mut)] +fn window_settings() -> window::Settings { + let mut settings = window::Settings { + position: Position::Centered, + min_size: Some(Size::new(700.0_f32, 700.0_f32)), + ..Default::default() + }; + + #[cfg(target_os = "linux")] + { + settings.platform_specific = PlatformSpecific { + application_id: "JapLearn".to_string(), + override_redirect: false, + }; + } + + settings +} + +fn subscription(_state: &ScreenState) -> Subscription { + keyboard::listen().map(Keyboard) +} + +pub struct AppState { + pub dictionary: Vec, + pub decks: Vec, + pub word_groups: Vec, + pub connection: Connection, + pub sync_data: AppSettings, + pub activity: HashMap>, +} + +impl Default for AppState { + fn default() -> Self { + Self::new() + } +} + +impl AppState { + pub fn new() -> Self { + Self { + dictionary: vec![], + decks: vec![], + word_groups: vec![], + connection: default_connection(), + sync_data: AppSettings { + key: None, + auto_web_fetch: false, + }, + activity: Default::default(), + } + } +} + +pub fn fill_state(state: &mut AppState) { + let list = load_words(&state.connection); + let sets = load_sets(&state.connection); + let groups = load_word_groups(&state.connection); + let setting = load_settings(&state.connection); + + state.dictionary = list; + state.decks = sets; + state.word_groups = groups; + state.sync_data = setting +} + +fn load_settings(connection: &Connection) -> AppSettings { + let key = get_setting("SYNC_KEY".to_string(), connection); + let fetch = get_setting("AUTO_WEB_FETCH".to_string(), connection) + .unwrap_or("false".to_string()) + .parse::() + .unwrap(); + AppSettings { + key, + auto_web_fetch: fetch, + } +} diff --git a/src/data_provider/import.rs b/src/data_provider/import.rs index 432bfa0..4b71653 100644 --- a/src/data_provider/import.rs +++ b/src/data_provider/import.rs @@ -1,3 +1,4 @@ +use crate::notification::NotificationData; use hashbrown::HashMap; use rusqlite::Connection; use serde_json::Value; @@ -21,58 +22,73 @@ pub struct ImportNote { pub fields: Vec, } -pub fn load_groups(connection: &Connection) -> ImportData { +pub fn load_groups(connection: &Connection) -> Result { let json_models: String = connection .query_one("select models from col", (), |row| row.get(0)) - .unwrap(); - let raw: Value = serde_json::from_str(json_models.as_str()).unwrap(); - let sets = raw.as_object().unwrap(); - let mut total_data = ImportData(vec![]); + .map_err(|_| NotificationData::IncorrectAnkiFile)?; + let raw: Value = serde_json::from_str(json_models.as_str()) + .map_err(|_| NotificationData::IncorrectAnkiFile)?; + let sets = raw.as_object(); + if let Some(sets) = sets { + let mut total_data = ImportData(vec![]); - let mut count_stmt = connection - .prepare("select mid, count(id) from notes group by mid") - .unwrap(); + let mut count_stmt = connection + .prepare("select mid, count(id) from notes group by mid") + .map_err(|_| NotificationData::IncorrectAnkiFile)?; - let counts = count_stmt - .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) - .unwrap() - .map(|x| x.unwrap()) - .collect::>(); + let counts = count_stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?))) + .unwrap() + .map(|x| x.unwrap()) + .collect::>(); - for (_, collection) in sets { - let fields_property = collection.get("fields"); - if let Some(fields_property) = fields_property { - let fields = fields_property - .as_array() - .unwrap() - .iter() - .map(|x| x["name"].as_str().unwrap().to_string()) - .collect(); - let mut group = ImportGroup { - id: collection["id"].as_u64().unwrap(), - name: collection["name"].as_str().unwrap().to_string(), - fields, - mapping: Default::default(), - length: 0, - imported: false, - }; - if let Some((_, count)) = counts.iter().find(|(id, _)| *id == group.id as i64) { - group.length = *count as u64 + for (_, collection) in sets { + let fields_property = collection.get("fields"); + if let Some(fields_property) = fields_property { + let fields = fields_property + .as_array() + .unwrap() + .iter() + .filter_map(|x| x.get("name")) + .map(|x| x.as_str().unwrap().to_string()) + .collect(); + let id = collection.get("id"); + if id.is_none() { + return Err(NotificationData::IncorrectAnkiFile); + } + let id = id.unwrap().as_u64().unwrap(); + let name = collection.get("name"); + if name.is_none() { + return Err(NotificationData::IncorrectAnkiFile); + } + let name = name.unwrap().as_str().unwrap().to_string(); + + let mut group = ImportGroup { + id, + name, + fields, + mapping: Default::default(), + length: 0, + imported: false, + }; + if let Some((_, count)) = counts.iter().find(|(id, _)| *id == group.id as i64) { + group.length = *count as u64 + } + println!("{:?}", group); + total_data.0.push(group); } - println!("{:?}", group); - total_data.0.push(group); } - + return Ok(total_data) } - total_data + Err(NotificationData::IncorrectAnkiFile) } -pub fn get_words_of_group(connection: &Connection, group_id: u64) -> Vec { +pub fn get_words_of_group(connection: &Connection, group_id: u64) -> Result, NotificationData> { let mut count_stmt = connection .prepare("select tags, flds from notes where mid == ?1;") - .unwrap(); + .map_err(|_| NotificationData::IncorrectAnkiFile)?; - count_stmt + Ok(count_stmt .query_map((group_id as i64,), |row| { Ok(ImportNote { tags: row.get(0)?, @@ -83,7 +99,7 @@ pub fn get_words_of_group(connection: &Connection, group_id: u64) -> Vec>() + .collect::>()) } diff --git a/src/data_provider/voice.rs b/src/data_provider/voice.rs index c24b8fd..92b9cd7 100644 --- a/src/data_provider/voice.rs +++ b/src/data_provider/voice.rs @@ -3,12 +3,14 @@ use reqwest::Client; use sha2::{Digest, Sha256}; use std::fs::File; use std::io::BufReader; +use crate::notification::NotificationData; +use crate::notification::NotificationData::*; -pub async fn get_voice(text: &str) -> Result, ()> { +pub async fn get_voice(text: &str) -> Result, NotificationData> { let mut path = app_data_dir(); path.push("voice"); if !path.exists() { - std::fs::create_dir(path.clone()).map_err(|_| ())?; + std::fs::create_dir(path.clone()).map_err(|_| PermissionDenied)?; } let hash = format!("{}.wav", hex::encode(Sha256::digest(text.as_bytes()))); path.push(hash); @@ -20,7 +22,7 @@ pub async fn get_voice(text: &str) -> Result, ()> { .send() .await; - let query = query.map_err(|_| ())?.text().await.map_err(|_| ())?; + let query = query.map_err(|_| VoiceVoxUnavailable)?.text().await.map_err(|_| VoiceVoxUnavailable)?; // Synthesis let audio = client .post(format!("{engine_url}/synthesis?speaker=11")) @@ -28,13 +30,13 @@ pub async fn get_voice(text: &str) -> Result, ()> { .body(query) .send() .await - .map_err(|_| ())? + .map_err(|_| VoiceVoxUnavailable)? .bytes() .await - .map_err(|_| ())?; + .map_err(|_| VoiceVoxUnavailable)?; - tokio::fs::write(&path, &audio).await.map_err(|_| ())?; + tokio::fs::write(&path, &audio).await.map_err(|_| PermissionDenied)?; } - Ok(BufReader::new(File::open(path).map_err(|_| ())?)) + Ok(BufReader::new(File::open(path).map_err(|_| PermissionDenied)?)) } diff --git a/src/dictionary.rs b/src/dictionary.rs index bcfe3b1..c1fcae6 100644 --- a/src/dictionary.rs +++ b/src/dictionary.rs @@ -4,10 +4,9 @@ use crate::dictionary_quiz::DictionaryQuizState; use crate::import::ImportState; use crate::lang::{WordData, WordGroup}; use crate::navigation::Page::{Import, Word}; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::styling::*; use crate::word::WordState; -use crate::{AppState, RootMessage}; use chrono::{DateTime, TimeDelta, Utc}; use hashbrown::{HashMap, HashSet}; use iced::alignment::Vertical::Center; @@ -25,6 +24,7 @@ use std::ops::Add; use std::path::PathBuf; use std::sync::Arc; use std::time::{Duration, Instant}; +use crate::app::AppState; #[derive(Clone)] pub struct DictionaryState { diff --git a/src/history.rs b/src/history.rs index 380477b..d854854 100644 --- a/src/history.rs +++ b/src/history.rs @@ -1,8 +1,7 @@ use crate::data_provider::history::{get_history_of_set, HistoryItem}; use crate::lang::{WordData, INVALID_ID}; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::*; use crate::styling::*; -use crate::{AppState, RootMessage}; use chrono::Local; use iced::alignment::Horizontal::Center; use iced::widget::space::horizontal; @@ -11,6 +10,7 @@ use iced::{Element, Fill, FillPortion, Task}; use parking_lot::Mutex; use std::sync::Arc; use HistoryMessage::Back; +use crate::app::AppState; #[derive(Clone)] pub struct HistoryState { diff --git a/src/import.rs b/src/import.rs index 0b05ad3..493c75a 100644 --- a/src/import.rs +++ b/src/import.rs @@ -1,10 +1,11 @@ -use crate::AppState; use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups}; use crate::data_provider::words::{add_group, add_words}; use crate::dictionary::app_data_dir; use crate::import::ImportMessage::*; use crate::lang::{WordData, WordGroup}; use crate::navigation::{NavigatedPage, Page, RootMessage}; +use crate::notification::NotificationData::OpenFileError; +use crate::notification::{NotificationData, NotificationRequest}; use crate::styling::*; use iced::widget::button::danger; use iced::widget::container::success; @@ -25,6 +26,7 @@ use std::path::PathBuf; use std::sync::Arc; use tokio::task::spawn_blocking; use zip::ZipArchive; +use crate::app::AppState; const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"]; @@ -275,9 +277,6 @@ impl ImportState { let mut row = Row::new(); for default in DEFAULT_FIELDS { - // if group.mapping.values().any(|name| name == default) { - // continue; - // } row = row.push( button(text!("{default}")).on_press(SetupDefaultProperty(default.to_string())), ); @@ -365,41 +364,38 @@ impl ImportState { .await .unwrap() }, - |result: Result| { - if let Ok(import) = result { - RootMessage::Import(UpdateImport(import)) - } else { - RootMessage::None - } + |result: Result| match result { + Ok(import) => RootMessage::Import(UpdateImport(import)), + Err(err) => RootMessage::PushNotification(NotificationRequest::Error(err)), }, ) } - fn extract_import_file(path: PathBuf) -> Result<(), ()> { - let file = File::open(path).map_err(|_| ())?; + fn extract_import_file(path: PathBuf) -> Result<(), NotificationData> { + let file = File::open(path).map_err(|_| OpenFileError)?; let reader = BufReader::new(file); - let mut archive = ZipArchive::new(reader).map_err(|_| ())?; + let mut archive = ZipArchive::new(reader).map_err(|_| OpenFileError)?; let temp_file_path = app_data_dir().join("import"); let mut temp_file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(&temp_file_path) - .map_err(|_| ())?; + .map_err(|_| OpenFileError)?; if let Ok(mut file) = archive.by_name("collection.anki21") { - std::io::copy(&mut file, &mut temp_file).map_err(|_| ())?; + std::io::copy(&mut file, &mut temp_file).map_err(|_| OpenFileError)?; Ok(()) } else if let Ok(mut file) = archive.by_name("collection.anki2") { - std::io::copy(&mut file, &mut temp_file).map_err(|_| ())?; + std::io::copy(&mut file, &mut temp_file).map_err(|_| OpenFileError)?; Ok(()) } else { - Err(()) + Err(OpenFileError) } } - fn read_import_file() -> Result { + fn read_import_file() -> Result { let temp_file_path = app_data_dir().join("import"); - let connection = Connection::open(&temp_file_path).map_err(|_| ())?; - let data = load_groups(&connection); + let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?; + let data = load_groups(&connection)?; Ok(data) } fn setup_mapping(&mut self, property_name: String) { @@ -435,8 +431,8 @@ impl ImportState { async move { spawn_blocking(move || { let temp_file_path = app_data_dir().join("import"); - let connection = Connection::open(&temp_file_path).map_err(|_| ())?; - let import_list = get_words_of_group(&connection, group.id); + let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?; + let import_list = get_words_of_group(&connection, group.id)?; let mut words_list = Vec::with_capacity(import_list.len()); let map_indices = Self::get_mapping_indices(&group); @@ -500,7 +496,10 @@ impl ImportState { .await .unwrap() }, - |_: Result<(), ()>| RootMessage::Import(ImportFinished), + |r: Result<(), NotificationData>| match r { + Ok(_) => RootMessage::Import(ImportFinished), + Err(err) => RootMessage::PushNotification(NotificationRequest::Error(err)) + } ) } diff --git a/src/lang.rs b/src/lang.rs index afae50e..a193781 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -1,7 +1,7 @@ use crate::data_provider::card_stats::load_stats_of_deck; use crate::data_provider::card_stats::update_stat_score; use crate::data_provider::history::{push_note, HistoryItem}; -use crate::AppState; +use crate::app::AppState; use chrono::{DateTime, Utc}; use parking_lot::Mutex; use rand::distr::weighted::WeightedIndex; diff --git a/src/main.rs b/src/main.rs index eb205f7..0a8f722 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,120 +17,18 @@ mod sync; mod word; mod writing; pub mod notification; +pub mod app; -use crate::RootMessage::Keyboard; -use crate::data_provider::card_sets::load_sets; -use crate::data_provider::settings::get_setting; -use crate::data_provider::sqlite::default_connection; -use crate::data_provider::words::{load_word_groups, load_words}; -use crate::lang::{DeckSettings, Id, WordData, WordGroup}; -use crate::navigation::{AppSettings, RootMessage, ScreenState}; +use crate::app::start; +use crate::navigation::RootMessage; use crate::quiz::*; -use chrono::NaiveDate; -use hashbrown::HashMap; -use iced::{Font, window}; -use iced::{Subscription, Theme, keyboard}; -use iced_core::Size; -use iced_core::window::Position; -#[cfg(target_os = "linux")] -use iced_core::window::settings::PlatformSpecific; use mimalloc::MiMalloc; -use rusqlite::Connection; + #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; -const USER_FONT: Font = Font::with_name("Noto Sans JP"); fn main() -> iced::Result { - println!("Welcome to JapLearn"); - iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) - .window(window_settings()) - .subscription(subscription) - .title("JapLearn") - .settings(iced::Settings { - default_text_size: iced::Pixels(18.0), - ..iced::Settings::default() - }) - .font(include_bytes!("../noto.ttf")) - .default_font(USER_FONT) - .theme(Theme::GruvboxDark) - .run() -} - -#[allow(unused_mut)] -fn window_settings() -> window::Settings { - let mut settings = window::Settings { - position: Position::Centered, - min_size: Some(Size::new(700.0_f32, 700.0_f32)), - ..Default::default() - }; - - #[cfg(target_os = "linux")] - { - settings.platform_specific = PlatformSpecific { - application_id: "JapLearn".to_string(), - override_redirect: false, - }; - } - - settings -} - -fn subscription(_state: &ScreenState) -> Subscription { - keyboard::listen().map(Keyboard) -} - -pub struct AppState { - pub dictionary: Vec, - pub decks: Vec, - pub word_groups: Vec, - pub connection: Connection, - pub sync_data: AppSettings, - pub activity: HashMap>, -} - -impl Default for AppState { - fn default() -> Self { - Self::new() - } -} - -impl AppState { - pub fn new() -> Self { - Self { - dictionary: vec![], - decks: vec![], - word_groups: vec![], - connection: default_connection(), - sync_data: AppSettings { - key: None, - auto_web_fetch: false, - }, - activity: Default::default(), - } - } -} - -fn fill_state(state: &mut AppState) { - let list = load_words(&state.connection); - let sets = load_sets(&state.connection); - let groups = load_word_groups(&state.connection); - let setting = load_settings(&state.connection); - - state.dictionary = list; - state.decks = sets; - state.word_groups = groups; - state.sync_data = setting -} - -fn load_settings(connection: &Connection) -> AppSettings { - let key = get_setting("SYNC_KEY".to_string(), connection); - let fetch = get_setting("AUTO_WEB_FETCH".to_string(), connection) - .unwrap_or("false".to_string()) - .parse::() - .unwrap(); - AppSettings { - key, - auto_web_fetch: fetch, - } + start() } + \ No newline at end of file diff --git a/src/navigation.rs b/src/navigation.rs index e1dcbe9..fa65d57 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -22,7 +22,7 @@ use crate::sync::{SyncMessage, SyncState}; use crate::view_navigation; use crate::word::{WordMessage, WordState}; use crate::writing::{WritingMessage, WritingState}; -use crate::{fill_state, AppState}; +use crate::app::{fill_state, AppState}; use chrono::NaiveDate; use hashbrown::HashMap; use iced::keyboard::Event; diff --git a/src/notification.rs b/src/notification.rs index 70111a7..d4319ea 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -132,6 +132,18 @@ impl NotificationManager { NotificationData::ImportError => { text!("Ошибка при импорте данных из Anki") } + NotificationData::IncorrectAnkiFile => { + text!("Неподдерживаемый формат файла .apkg") + } + NotificationData::OpenFileError => { + text!("Ошибка открытия файла пакета") + } + NotificationData::VoiceVoxUnavailable => { + text!("Не получается подключиться к VoiceVox") + } + NotificationData::PermissionDenied => { + text!("У приложение недостаточно прав") + } } .into() } @@ -146,6 +158,10 @@ pub enum NotificationRequest { #[derive(Clone)] pub enum NotificationData { + PermissionDenied, DataLoaded, ImportError, + IncorrectAnkiFile, + OpenFileError, + VoiceVoxUnavailable } diff --git a/src/quiz.rs b/src/quiz.rs index 7e8be3e..facc1ab 100644 --- a/src/quiz.rs +++ b/src/quiz.rs @@ -1,12 +1,12 @@ use crate::lang::KanaSet; use crate::navigation::Page::PreviousPage; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::quiz::QuizMessage::*; use crate::styling::*; -use crate::{RootMessage, USER_FONT}; use iced::widget::*; use iced::{Element, Fill, Task, alignment}; use rand::seq::SliceRandom; +use crate::app::USER_FONT; #[derive(Clone, Debug)] pub struct QuizState { diff --git a/src/repetition.rs b/src/repetition.rs index 7ef3db8..55d4ceb 100644 --- a/src/repetition.rs +++ b/src/repetition.rs @@ -2,9 +2,8 @@ use crate::data_provider::voice::get_voice; use crate::lang::Id; use crate::lang::{CardStatistics, DeckData, DeckSettings, WordData, WordOpenMode}; use crate::navigation::Page::PreviousPage; -use crate::navigation::{KeyPressedPage, NavigatedPage, Page}; +use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage}; use crate::styling::*; -use crate::{AppState, RootMessage}; use chrono::Local; use iced::alignment::Horizontal::Center; use iced::keyboard::key::Physical::Code; @@ -16,6 +15,7 @@ use rodio::MixerDeviceSink; use std::collections::HashSet; use std::sync::Arc; use tokio::task::spawn_blocking; +use crate::app::AppState; pub struct RepetitionState { state: Arc>, diff --git a/src/repetition_settings.rs b/src/repetition_settings.rs index 48db074..fe99dc5 100644 --- a/src/repetition_settings.rs +++ b/src/repetition_settings.rs @@ -1,4 +1,4 @@ -use crate::AppState; + use crate::data_provider::card_sets::{delete_set, update_deck}; use crate::data_provider::card_stats::{delete_stats, load_stats_of_deck}; use crate::lang::DeckSettings; @@ -15,6 +15,7 @@ use iced_core::{Padding, Theme, color}; use parking_lot::Mutex; use std::sync::Arc; use std::time::Duration; +use crate::app::AppState; pub struct RepetitionSettingsState { set: DeckSettings, diff --git a/src/repetitions.rs b/src/repetitions.rs index 846ee5b..bf889d3 100644 --- a/src/repetitions.rs +++ b/src/repetitions.rs @@ -3,12 +3,11 @@ use crate::data_provider::card_stats::{add_stat_list, load_stats_of_deck}; use crate::history::HistoryState; use crate::lang::{AppendMode, CardStatistics, DeckSettings, Id, OrderMode}; use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings}; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::repetition::RepetitionState; use crate::repetition_settings::RepetitionSettingsState; use crate::repetitions::RepetitionsMessage::*; use crate::styling::*; -use crate::{AppState, RootMessage}; use chrono::{Days, Local, Utc}; use hashbrown::{HashMap, HashSet}; pub use iced::widget::button::{Catalog, Style}; @@ -25,6 +24,7 @@ use iced_core::svg::Handle; use parking_lot::Mutex; use std::ops::Deref; use std::sync::Arc; +use crate::app::AppState; #[derive(Clone)] pub struct RepetitionsState { diff --git a/src/selector.rs b/src/selector.rs index 3be8cac..a1eaced 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,14 +1,13 @@ use crate::dictionary::DictionaryState; use crate::lang::{KanaSet, KanaType}; use crate::navigation::Page::*; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::randomizer::RandomizerState; use crate::repetitions::RepetitionsState; use crate::selector::SelectorMessage::ChangeMode; use crate::styling::*; use crate::sync::SyncState; use crate::writing::WritingState; -use crate::{AppState, QuizState, RootMessage}; use iced::widget::button::{Status, Style}; use iced::widget::*; use iced::{Element, Task, alignment}; @@ -17,6 +16,8 @@ use iced_core::Background; use iced_core::Color; use parking_lot::Mutex; use std::sync::Arc; +use crate::app::AppState; +use crate::quiz::QuizState; pub struct SelectorState { pub set: KanaSet, diff --git a/src/sync.rs b/src/sync.rs index 1733211..72b3b77 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -1,10 +1,9 @@ use crate::data_provider::settings::{delete_settings, set_setting}; use crate::data_provider::web_api::*; use crate::navigation::Page::PreviousPage; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::styling::*; use crate::sync::SyncMessage::*; -use crate::{fill_state, AppState, RootMessage}; use iced::widget::button::danger; use iced::widget::container::rounded_box; use iced::widget::{button, column, container, progress_bar, row, space, text, toggler}; @@ -12,6 +11,7 @@ use iced::{Center, Element, Fill, Font, Length, Task}; use parking_lot::Mutex; use std::sync::Arc; use std::time::Duration; +use crate::app::{fill_state, AppState}; use crate::notification::{NotificationRequest, NotificationData}; #[derive(Clone)] diff --git a/src/word.rs b/src/word.rs index 5daea7d..84ae7fc 100644 --- a/src/word.rs +++ b/src/word.rs @@ -1,15 +1,15 @@ use crate::data_provider::words::{delete_word, update_word}; use crate::lang::WordData; use crate::navigation::Page::PreviousPage; -use crate::navigation::{NavigatedPage, Page}; +use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::styling::*; use crate::word::WordMessage::*; -use crate::{AppState, RootMessage}; use iced::widget::button::danger; use iced::widget::{button, column, row, rule, scrollable, text, text_input}; use iced::{Element, Fill, Task}; use parking_lot::Mutex; use std::sync::Arc; +use crate::app::AppState; #[derive(Clone)] pub struct WordState {