More notifications and move start code to another file

This commit is contained in:
micialware committed 2026-08-23 21:49:01 +03:00
1 parent e1bfd9494d
commit 5a926b1419
17 files changed
+240 -198

No files matched your search

+109
View File
@@ -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<RootMessage> {
keyboard::listen().map(Keyboard)
}
pub struct AppState {
pub dictionary: Vec<WordData>,
pub decks: Vec<DeckSettings>,
pub word_groups: Vec<WordGroup>,
pub connection: Connection,
pub sync_data: AppSettings,
pub activity: HashMap<Id, Vec<(NaiveDate, u32)>>,
}
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::<bool>()
.unwrap();
AppSettings {
key,
auto_web_fetch: fetch,
}
}
+57 -41
View File
@@ -1,3 +1,4 @@
use crate::notification::NotificationData;
use hashbrown::HashMap; use hashbrown::HashMap;
use rusqlite::Connection; use rusqlite::Connection;
use serde_json::Value; use serde_json::Value;
@@ -21,58 +22,73 @@ pub struct ImportNote {
pub fields: Vec<String>, pub fields: Vec<String>,
} }
pub fn load_groups(connection: &Connection) -> ImportData { pub fn load_groups(connection: &Connection) -> Result<ImportData, NotificationData> {
let json_models: String = connection let json_models: String = connection
.query_one("select models from col", (), |row| row.get(0)) .query_one("select models from col", (), |row| row.get(0))
.unwrap(); .map_err(|_| NotificationData::IncorrectAnkiFile)?;
let raw: Value = serde_json::from_str(json_models.as_str()).unwrap(); let raw: Value = serde_json::from_str(json_models.as_str())
let sets = raw.as_object().unwrap(); .map_err(|_| NotificationData::IncorrectAnkiFile)?;
let mut total_data = ImportData(vec![]); let sets = raw.as_object();
if let Some(sets) = sets {
let mut total_data = ImportData(vec![]);
let mut count_stmt = connection let mut count_stmt = connection
.prepare("select mid, count(id) from notes group by mid") .prepare("select mid, count(id) from notes group by mid")
.unwrap(); .map_err(|_| NotificationData::IncorrectAnkiFile)?;
let counts = count_stmt let counts = count_stmt
.query_map([], |row| Ok((row.get(0)?, row.get(1)?))) .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.unwrap() .unwrap()
.map(|x| x.unwrap()) .map(|x| x.unwrap())
.collect::<Vec<(i64, i64)>>(); .collect::<Vec<(i64, i64)>>();
for (_, collection) in sets { for (_, collection) in sets {
let fields_property = collection.get("fields"); let fields_property = collection.get("fields");
if let Some(fields_property) = fields_property { if let Some(fields_property) = fields_property {
let fields = fields_property let fields = fields_property
.as_array() .as_array()
.unwrap() .unwrap()
.iter() .iter()
.map(|x| x["name"].as_str().unwrap().to_string()) .filter_map(|x| x.get("name"))
.collect(); .map(|x| x.as_str().unwrap().to_string())
let mut group = ImportGroup { .collect();
id: collection["id"].as_u64().unwrap(), let id = collection.get("id");
name: collection["name"].as_str().unwrap().to_string(), if id.is_none() {
fields, return Err(NotificationData::IncorrectAnkiFile);
mapping: Default::default(), }
length: 0, let id = id.unwrap().as_u64().unwrap();
imported: false, let name = collection.get("name");
}; if name.is_none() {
if let Some((_, count)) = counts.iter().find(|(id, _)| *id == group.id as i64) { return Err(NotificationData::IncorrectAnkiFile);
group.length = *count as u64 }
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<ImportNote> { pub fn get_words_of_group(connection: &Connection, group_id: u64) -> Result<Vec<ImportNote>, NotificationData> {
let mut count_stmt = connection let mut count_stmt = connection
.prepare("select tags, flds from notes where mid == ?1;") .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| { .query_map((group_id as i64,), |row| {
Ok(ImportNote { Ok(ImportNote {
tags: row.get(0)?, tags: row.get(0)?,
@@ -83,7 +99,7 @@ pub fn get_words_of_group(connection: &Connection, group_id: u64) -> Vec<ImportN
.collect(), .collect(),
}) })
}) })
.unwrap() .map_err(|_| NotificationData::IncorrectAnkiFile)?
.map(|x| x.unwrap()) .map(|x| x.unwrap())
.collect::<Vec<ImportNote>>() .collect::<Vec<ImportNote>>())
} }
+9 -7
View File
@@ -3,12 +3,14 @@ use reqwest::Client;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use crate::notification::NotificationData;
use crate::notification::NotificationData::*;
pub async fn get_voice(text: &str) -> Result<BufReader<File>, ()> { pub async fn get_voice(text: &str) -> Result<BufReader<File>, NotificationData> {
let mut path = app_data_dir(); let mut path = app_data_dir();
path.push("voice"); path.push("voice");
if !path.exists() { 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()))); let hash = format!("{}.wav", hex::encode(Sha256::digest(text.as_bytes())));
path.push(hash); path.push(hash);
@@ -20,7 +22,7 @@ pub async fn get_voice(text: &str) -> Result<BufReader<File>, ()> {
.send() .send()
.await; .await;
let query = query.map_err(|_| ())?.text().await.map_err(|_| ())?; let query = query.map_err(|_| VoiceVoxUnavailable)?.text().await.map_err(|_| VoiceVoxUnavailable)?;
// Synthesis // Synthesis
let audio = client let audio = client
.post(format!("{engine_url}/synthesis?speaker=11")) .post(format!("{engine_url}/synthesis?speaker=11"))
@@ -28,13 +30,13 @@ pub async fn get_voice(text: &str) -> Result<BufReader<File>, ()> {
.body(query) .body(query)
.send() .send()
.await .await
.map_err(|_| ())? .map_err(|_| VoiceVoxUnavailable)?
.bytes() .bytes()
.await .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)?))
} }
+2 -2
View File
@@ -4,10 +4,9 @@ use crate::dictionary_quiz::DictionaryQuizState;
use crate::import::ImportState; use crate::import::ImportState;
use crate::lang::{WordData, WordGroup}; use crate::lang::{WordData, WordGroup};
use crate::navigation::Page::{Import, Word}; use crate::navigation::Page::{Import, Word};
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*; use crate::styling::*;
use crate::word::WordState; use crate::word::WordState;
use crate::{AppState, RootMessage};
use chrono::{DateTime, TimeDelta, Utc}; use chrono::{DateTime, TimeDelta, Utc};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use iced::alignment::Vertical::Center; use iced::alignment::Vertical::Center;
@@ -25,6 +24,7 @@ use std::ops::Add;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::app::AppState;
#[derive(Clone)] #[derive(Clone)]
pub struct DictionaryState { pub struct DictionaryState {
+2 -2
View File
@@ -1,8 +1,7 @@
use crate::data_provider::history::{get_history_of_set, HistoryItem}; use crate::data_provider::history::{get_history_of_set, HistoryItem};
use crate::lang::{WordData, INVALID_ID}; use crate::lang::{WordData, INVALID_ID};
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::*;
use crate::styling::*; use crate::styling::*;
use crate::{AppState, RootMessage};
use chrono::Local; use chrono::Local;
use iced::alignment::Horizontal::Center; use iced::alignment::Horizontal::Center;
use iced::widget::space::horizontal; use iced::widget::space::horizontal;
@@ -11,6 +10,7 @@ use iced::{Element, Fill, FillPortion, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use HistoryMessage::Back; use HistoryMessage::Back;
use crate::app::AppState;
#[derive(Clone)] #[derive(Clone)]
pub struct HistoryState { pub struct HistoryState {
+22 -23
View File
@@ -1,10 +1,11 @@
use crate::AppState;
use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups}; use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups};
use crate::data_provider::words::{add_group, add_words}; use crate::data_provider::words::{add_group, add_words};
use crate::dictionary::app_data_dir; use crate::dictionary::app_data_dir;
use crate::import::ImportMessage::*; use crate::import::ImportMessage::*;
use crate::lang::{WordData, WordGroup}; use crate::lang::{WordData, WordGroup};
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::NotificationData::OpenFileError;
use crate::notification::{NotificationData, NotificationRequest};
use crate::styling::*; use crate::styling::*;
use iced::widget::button::danger; use iced::widget::button::danger;
use iced::widget::container::success; use iced::widget::container::success;
@@ -25,6 +26,7 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use zip::ZipArchive; use zip::ZipArchive;
use crate::app::AppState;
const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"]; const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];
@@ -275,9 +277,6 @@ impl ImportState {
let mut row = Row::new(); let mut row = Row::new();
for default in DEFAULT_FIELDS { for default in DEFAULT_FIELDS {
// if group.mapping.values().any(|name| name == default) {
// continue;
// }
row = row.push( row = row.push(
button(text!("{default}")).on_press(SetupDefaultProperty(default.to_string())), button(text!("{default}")).on_press(SetupDefaultProperty(default.to_string())),
); );
@@ -365,41 +364,38 @@ impl ImportState {
.await .await
.unwrap() .unwrap()
}, },
|result: Result<ImportData, ()>| { |result: Result<ImportData, NotificationData>| match result {
if let Ok(import) = result { Ok(import) => RootMessage::Import(UpdateImport(import)),
RootMessage::Import(UpdateImport(import)) Err(err) => RootMessage::PushNotification(NotificationRequest::Error(err)),
} else {
RootMessage::None
}
}, },
) )
} }
fn extract_import_file(path: PathBuf) -> Result<(), ()> { fn extract_import_file(path: PathBuf) -> Result<(), NotificationData> {
let file = File::open(path).map_err(|_| ())?; let file = File::open(path).map_err(|_| OpenFileError)?;
let reader = BufReader::new(file); 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 temp_file_path = app_data_dir().join("import");
let mut temp_file = OpenOptions::new() let mut temp_file = OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
.truncate(true) .truncate(true)
.open(&temp_file_path) .open(&temp_file_path)
.map_err(|_| ())?; .map_err(|_| OpenFileError)?;
if let Ok(mut file) = archive.by_name("collection.anki21") { 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(()) Ok(())
} else if let Ok(mut file) = archive.by_name("collection.anki2") { } 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(()) Ok(())
} else { } else {
Err(()) Err(OpenFileError)
} }
} }
fn read_import_file() -> Result<ImportData, ()> { fn read_import_file() -> Result<ImportData, NotificationData> {
let temp_file_path = app_data_dir().join("import"); let temp_file_path = app_data_dir().join("import");
let connection = Connection::open(&temp_file_path).map_err(|_| ())?; let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
let data = load_groups(&connection); let data = load_groups(&connection)?;
Ok(data) Ok(data)
} }
fn setup_mapping(&mut self, property_name: String) { fn setup_mapping(&mut self, property_name: String) {
@@ -435,8 +431,8 @@ impl ImportState {
async move { async move {
spawn_blocking(move || { spawn_blocking(move || {
let temp_file_path = app_data_dir().join("import"); let temp_file_path = app_data_dir().join("import");
let connection = Connection::open(&temp_file_path).map_err(|_| ())?; let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
let import_list = get_words_of_group(&connection, group.id); let import_list = get_words_of_group(&connection, group.id)?;
let mut words_list = Vec::with_capacity(import_list.len()); let mut words_list = Vec::with_capacity(import_list.len());
let map_indices = Self::get_mapping_indices(&group); let map_indices = Self::get_mapping_indices(&group);
@@ -500,7 +496,10 @@ impl ImportState {
.await .await
.unwrap() .unwrap()
}, },
|_: Result<(), ()>| RootMessage::Import(ImportFinished), |r: Result<(), NotificationData>| match r {
Ok(_) => RootMessage::Import(ImportFinished),
Err(err) => RootMessage::PushNotification(NotificationRequest::Error(err))
}
) )
} }
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::data_provider::card_stats::load_stats_of_deck; use crate::data_provider::card_stats::load_stats_of_deck;
use crate::data_provider::card_stats::update_stat_score; use crate::data_provider::card_stats::update_stat_score;
use crate::data_provider::history::{push_note, HistoryItem}; use crate::data_provider::history::{push_note, HistoryItem};
use crate::AppState; use crate::app::AppState;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::distr::weighted::WeightedIndex; use rand::distr::weighted::WeightedIndex;
+6 -108
View File
@@ -17,120 +17,18 @@ mod sync;
mod word; mod word;
mod writing; mod writing;
pub mod notification; pub mod notification;
pub mod app;
use crate::RootMessage::Keyboard; use crate::app::start;
use crate::data_provider::card_sets::load_sets; use crate::navigation::RootMessage;
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::quiz::*; 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 mimalloc::MiMalloc;
use rusqlite::Connection;
#[global_allocator] #[global_allocator]
static GLOBAL: MiMalloc = MiMalloc; static GLOBAL: MiMalloc = MiMalloc;
const USER_FONT: Font = Font::with_name("Noto Sans JP");
fn main() -> iced::Result { fn main() -> iced::Result {
println!("Welcome to JapLearn"); start()
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<RootMessage> {
keyboard::listen().map(Keyboard)
}
pub struct AppState {
pub dictionary: Vec<WordData>,
pub decks: Vec<DeckSettings>,
pub word_groups: Vec<WordGroup>,
pub connection: Connection,
pub sync_data: AppSettings,
pub activity: HashMap<Id, Vec<(NaiveDate, u32)>>,
}
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::<bool>()
.unwrap();
AppSettings {
key,
auto_web_fetch: fetch,
}
} }
+1 -1
View File
@@ -22,7 +22,7 @@ use crate::sync::{SyncMessage, SyncState};
use crate::view_navigation; use crate::view_navigation;
use crate::word::{WordMessage, WordState}; use crate::word::{WordMessage, WordState};
use crate::writing::{WritingMessage, WritingState}; use crate::writing::{WritingMessage, WritingState};
use crate::{fill_state, AppState}; use crate::app::{fill_state, AppState};
use chrono::NaiveDate; use chrono::NaiveDate;
use hashbrown::HashMap; use hashbrown::HashMap;
use iced::keyboard::Event; use iced::keyboard::Event;
+16
View File
@@ -132,6 +132,18 @@ impl NotificationManager {
NotificationData::ImportError => { NotificationData::ImportError => {
text!("Ошибка при импорте данных из Anki") text!("Ошибка при импорте данных из Anki")
} }
NotificationData::IncorrectAnkiFile => {
text!("Неподдерживаемый формат файла .apkg")
}
NotificationData::OpenFileError => {
text!("Ошибка открытия файла пакета")
}
NotificationData::VoiceVoxUnavailable => {
text!("Не получается подключиться к VoiceVox")
}
NotificationData::PermissionDenied => {
text!("У приложение недостаточно прав")
}
} }
.into() .into()
} }
@@ -146,6 +158,10 @@ pub enum NotificationRequest {
#[derive(Clone)] #[derive(Clone)]
pub enum NotificationData { pub enum NotificationData {
PermissionDenied,
DataLoaded, DataLoaded,
ImportError, ImportError,
IncorrectAnkiFile,
OpenFileError,
VoiceVoxUnavailable
} }
+2 -2
View File
@@ -1,12 +1,12 @@
use crate::lang::KanaSet; use crate::lang::KanaSet;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::quiz::QuizMessage::*; use crate::quiz::QuizMessage::*;
use crate::styling::*; use crate::styling::*;
use crate::{RootMessage, USER_FONT};
use iced::widget::*; use iced::widget::*;
use iced::{Element, Fill, Task, alignment}; use iced::{Element, Fill, Task, alignment};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use crate::app::USER_FONT;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct QuizState { pub struct QuizState {
+2 -2
View File
@@ -2,9 +2,8 @@ use crate::data_provider::voice::get_voice;
use crate::lang::Id; use crate::lang::Id;
use crate::lang::{CardStatistics, DeckData, DeckSettings, WordData, WordOpenMode}; use crate::lang::{CardStatistics, DeckData, DeckSettings, WordData, WordOpenMode};
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{KeyPressedPage, NavigatedPage, Page}; use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage};
use crate::styling::*; use crate::styling::*;
use crate::{AppState, RootMessage};
use chrono::Local; use chrono::Local;
use iced::alignment::Horizontal::Center; use iced::alignment::Horizontal::Center;
use iced::keyboard::key::Physical::Code; use iced::keyboard::key::Physical::Code;
@@ -16,6 +15,7 @@ use rodio::MixerDeviceSink;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use crate::app::AppState;
pub struct RepetitionState { pub struct RepetitionState {
state: Arc<Mutex<AppState>>, state: Arc<Mutex<AppState>>,
+2 -1
View File
@@ -1,4 +1,4 @@
use crate::AppState;
use crate::data_provider::card_sets::{delete_set, update_deck}; use crate::data_provider::card_sets::{delete_set, update_deck};
use crate::data_provider::card_stats::{delete_stats, load_stats_of_deck}; use crate::data_provider::card_stats::{delete_stats, load_stats_of_deck};
use crate::lang::DeckSettings; use crate::lang::DeckSettings;
@@ -15,6 +15,7 @@ use iced_core::{Padding, Theme, color};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use crate::app::AppState;
pub struct RepetitionSettingsState { pub struct RepetitionSettingsState {
set: DeckSettings, set: DeckSettings,
+2 -2
View File
@@ -3,12 +3,11 @@ use crate::data_provider::card_stats::{add_stat_list, load_stats_of_deck};
use crate::history::HistoryState; use crate::history::HistoryState;
use crate::lang::{AppendMode, CardStatistics, DeckSettings, Id, OrderMode}; use crate::lang::{AppendMode, CardStatistics, DeckSettings, Id, OrderMode};
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings}; 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::RepetitionState;
use crate::repetition_settings::RepetitionSettingsState; use crate::repetition_settings::RepetitionSettingsState;
use crate::repetitions::RepetitionsMessage::*; use crate::repetitions::RepetitionsMessage::*;
use crate::styling::*; use crate::styling::*;
use crate::{AppState, RootMessage};
use chrono::{Days, Local, Utc}; use chrono::{Days, Local, Utc};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
pub use iced::widget::button::{Catalog, Style}; pub use iced::widget::button::{Catalog, Style};
@@ -25,6 +24,7 @@ use iced_core::svg::Handle;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use crate::app::AppState;
#[derive(Clone)] #[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
+3 -2
View File
@@ -1,14 +1,13 @@
use crate::dictionary::DictionaryState; use crate::dictionary::DictionaryState;
use crate::lang::{KanaSet, KanaType}; use crate::lang::{KanaSet, KanaType};
use crate::navigation::Page::*; use crate::navigation::Page::*;
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::randomizer::RandomizerState; use crate::randomizer::RandomizerState;
use crate::repetitions::RepetitionsState; use crate::repetitions::RepetitionsState;
use crate::selector::SelectorMessage::ChangeMode; use crate::selector::SelectorMessage::ChangeMode;
use crate::styling::*; use crate::styling::*;
use crate::sync::SyncState; use crate::sync::SyncState;
use crate::writing::WritingState; use crate::writing::WritingState;
use crate::{AppState, QuizState, RootMessage};
use iced::widget::button::{Status, Style}; use iced::widget::button::{Status, Style};
use iced::widget::*; use iced::widget::*;
use iced::{Element, Task, alignment}; use iced::{Element, Task, alignment};
@@ -17,6 +16,8 @@ use iced_core::Background;
use iced_core::Color; use iced_core::Color;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use crate::app::AppState;
use crate::quiz::QuizState;
pub struct SelectorState { pub struct SelectorState {
pub set: KanaSet, pub set: KanaSet,
+2 -2
View File
@@ -1,10 +1,9 @@
use crate::data_provider::settings::{delete_settings, set_setting}; use crate::data_provider::settings::{delete_settings, set_setting};
use crate::data_provider::web_api::*; use crate::data_provider::web_api::*;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*; use crate::styling::*;
use crate::sync::SyncMessage::*; use crate::sync::SyncMessage::*;
use crate::{fill_state, AppState, RootMessage};
use iced::widget::button::danger; use iced::widget::button::danger;
use iced::widget::container::rounded_box; use iced::widget::container::rounded_box;
use iced::widget::{button, column, container, progress_bar, row, space, text, toggler}; 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 parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use crate::app::{fill_state, AppState};
use crate::notification::{NotificationRequest, NotificationData}; use crate::notification::{NotificationRequest, NotificationData};
#[derive(Clone)] #[derive(Clone)]
+2 -2
View File
@@ -1,15 +1,15 @@
use crate::data_provider::words::{delete_word, update_word}; use crate::data_provider::words::{delete_word, update_word};
use crate::lang::WordData; use crate::lang::WordData;
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*; use crate::styling::*;
use crate::word::WordMessage::*; use crate::word::WordMessage::*;
use crate::{AppState, RootMessage};
use iced::widget::button::danger; use iced::widget::button::danger;
use iced::widget::{button, column, row, rule, scrollable, text, text_input}; use iced::widget::{button, column, row, rule, scrollable, text, text_input};
use iced::{Element, Fill, Task}; use iced::{Element, Fill, Task};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::Arc; use std::sync::Arc;
use crate::app::AppState;
#[derive(Clone)] #[derive(Clone)]
pub struct WordState { pub struct WordState {