Splitting kana set

This commit is contained in:
micialware committed 2026-08-24 01:22:00 +03:00
1 parent 36e5693564
commit c74a0eb311
25 files changed
+367 -332

No files matched your search

+1
View File
@@ -0,0 +1 @@
pub mod kana_set;
+201
View File
@@ -0,0 +1,201 @@
#[derive(Clone, Debug)]
pub struct KanaSet {
name: String,
pub(crate) chars_type: KanaType,
pub(crate) dictionary: Vec<Vec<(String, String)>>,
pub(crate) include_map: [bool; 10],
}
#[derive(Clone, Debug)]
pub enum KanaType {
Hiragana,
Katakana,
}
impl KanaSet {
pub fn hiragana() -> Self {
Self {
name: "Хирагана".to_string(),
chars_type: KanaType::Hiragana,
dictionary: vec![
vec![
(String::from("ぁ"), String::from("a")),
(String::from("ぃ"), String::from("i")),
(String::from("ぅ"), String::from("u")),
(String::from("ぇ"), String::from("e")),
(String::from("ぉ"), String::from("o")),
],
vec![
(String::from("さ"), String::from("sa")),
(String::from("し"), String::from("shi")),
(String::from("す"), String::from("su")),
(String::from("せ"), String::from("se")),
(String::from("そ"), String::from("so")),
],
vec![
(String::from("か"), String::from("ka")),
(String::from("き"), String::from("ki")),
(String::from("く"), String::from("ku")),
(String::from("け"), String::from("ke")),
(String::from("こ"), String::from("ko")),
],
// Ряд «та» (たちつてと)
vec![
(String::from("た"), String::from("ta")),
(String::from("ち"), String::from("chi")),
(String::from("つ"), String::from("tsu")),
(String::from("て"), String::from("te")),
(String::from("と"), String::from("to")),
],
// Ряд «на» (なにぬねの)
vec![
(String::from("な"), String::from("na")),
(String::from("に"), String::from("ni")),
(String::from("ぬ"), String::from("nu")),
(String::from("ね"), String::from("ne")),
(String::from("の"), String::from("no")),
],
// Ряд «ха» (はひふへほ)
vec![
(String::from("は"), String::from("ha")),
(String::from("ひ"), String::from("hi")),
(String::from("ふ"), String::from("fu")),
(String::from("へ"), String::from("he")),
(String::from("ほ"), String::from("ho")),
],
// Ряд «ма» (まみむめも)
vec![
(String::from("ま"), String::from("ma")),
(String::from("み"), String::from("mi")),
(String::from("む"), String::from("mu")),
(String::from("め"), String::from("me")),
(String::from("も"), String::from("mo")),
],
// Ряд «я» (やゆよ) — только 3 символа
vec![
(String::from("や"), String::from("ya")),
(String::from("ゆ"), String::from("yu")),
(String::from("よ"), String::from("yo")),
],
// Ряд «ра» (らりるれろ)
vec![
(String::from("ら"), String::from("ra")),
(String::from("り"), String::from("ri")),
(String::from("る"), String::from("ru")),
(String::from("れ"), String::from("re")),
(String::from("ろ"), String::from("ro")),
],
vec![
(String::from("わ"), String::from("wa")),
(String::from("を"), String::from("wo")),
(String::from("ん"), String::from("n")),
],
],
include_map: [true; 10],
}
}
pub fn katakana() -> Self {
Self {
name: "Катакана".to_string(),
chars_type: KanaType::Katakana,
dictionary: vec![
vec![
(String::from("ァ"), String::from("a")),
(String::from("ィ"), String::from("i")),
(String::from("ゥ"), String::from("u")),
(String::from("ェ"), String::from("e")),
(String::from("ォ"), String::from("o")),
],
vec![
(String::from("サ"), String::from("sa")),
(String::from("シ"), String::from("shi")),
(String::from("ス"), String::from("su")),
(String::from("セ"), String::from("se")),
(String::from("ソ"), String::from("so")),
],
vec![
(String::from("カ"), String::from("ka")),
(String::from("キ"), String::from("ki")),
(String::from("ク"), String::from("ku")),
(String::from("ケ"), String::from("ke")),
(String::from("コ"), String::from("ko")),
],
// Ряд «та» (タチツテト)
vec![
(String::from("タ"), String::from("ta")),
(String::from("チ"), String::from("chi")),
(String::from("ツ"), String::from("tsu")),
(String::from("テ"), String::from("te")),
(String::from("ト"), String::from("to")),
],
// Ряд «на» (ナニヌネノ)
vec![
(String::from("ナ"), String::from("na")),
(String::from("ニ"), String::from("ni")),
(String::from("ヌ"), String::from("nu")),
(String::from("ネ"), String::from("ne")),
(String::from("ノ"), String::from("no")),
],
// Ряд «ха» (ハヒフヘホ)
vec![
(String::from("ハ"), String::from("ha")),
(String::from("ヒ"), String::from("hi")),
(String::from("フ"), String::from("fu")),
(String::from("ヘ"), String::from("he")),
(String::from("ホ"), String::from("ho")),
],
// Ряд «ма» (マミムメモ)
vec![
(String::from("マ"), String::from("ma")),
(String::from("ミ"), String::from("mi")),
(String::from("ム"), String::from("mu")),
(String::from("メ"), String::from("me")),
(String::from("モ"), String::from("mo")),
],
// Ряд «я» (ヤユヨ) — только 3 символа
vec![
(String::from("ヤ"), String::from("ya")),
(String::from("ユ"), String::from("yu")),
(String::from("ヨ"), String::from("yo")),
],
// Ряд «ра» (ラリルレロ)
vec![
(String::from("ラ"), String::from("ra")),
(String::from("リ"), String::from("ri")),
(String::from("ル"), String::from("ru")),
(String::from("レ"), String::from("re")),
(String::from("ロ"), String::from("ro")),
],
vec![
(String::from("ワ"), String::from("wa")),
(String::from("ヲ"), String::from("wo")),
(String::from("ン"), String::from("n")),
],
],
include_map: [true; 10],
}
}
pub fn list(&self) -> Vec<(String, String)> {
let mut current_set: Vec<(String, String)> = Vec::new();
for i in 0..10 {
if self.include_map[i] {
self.dictionary[i].iter().for_each(|v| {
current_set.push(v.clone());
});
}
}
current_set
}
}
impl Default for KanaSet {
fn default() -> Self {
KanaSet::hiragana()
}
}
impl PartialEq<Self> for KanaSet {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
+11 -17
View File
@@ -1,20 +1,21 @@
use crate::dictionary::app_data_dir;
use crate::fs::*;
use crate::lang::{Id, WordOpenMode};
use chrono::{DateTime, Utc};
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use crate::notification::NotificationData;
use crate::notification::NotificationData::PermissionDenied;
pub fn get_history_of_set(id: Id) -> Vec<HistoryItem> {
let app_dir = history_dir();
pub fn get_history_of_set(id: Id) -> Result<Vec<HistoryItem>, NotificationData> {
let app_dir = history_dir()?;
let head = app_dir.clone().join(format!("set_{}_history.csv", id));
let mut lines = Vec::new();
append_lines_if_exists(head, &mut lines);
lines.reverse();
parse_history_items(lines)
Ok(parse_history_items(lines))
}
fn append_lines_if_exists(path: PathBuf, lines: &mut Vec<String>) {
@@ -56,15 +57,15 @@ fn parse_history_items(strings: Vec<String>) -> Vec<HistoryItem> {
items
}
pub fn push_note(set_id: Id, item: HistoryItem) {
let app_dir = history_dir();
pub fn push_note(set_id: Id, item: HistoryItem) -> Result<(), NotificationData> {
let app_dir = history_dir()?;
let path = app_dir.clone().join(format!("set_{}_history.csv", set_id));
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(path)
.unwrap();
.map_err(|_| PermissionDenied)?;
let line_str = format!(
"{};{};{};{};{}",
item.timestamp.timestamp(),
@@ -78,15 +79,8 @@ pub fn push_note(set_id: Id, item: HistoryItem) {
item.before,
item.after
);
writeln!(&mut file, "{}", line_str).unwrap();
}
pub fn history_dir() -> PathBuf {
let directory = app_data_dir().join("history");
if !directory.exists() {
fs::create_dir(directory.clone()).unwrap();
}
directory
writeln!(&mut file, "{}", line_str).map_err(|_| PermissionDenied)?;
Ok(())
}
#[derive(Clone)]
+3 -5
View File
@@ -1,9 +1,8 @@
use crate::dictionary::app_data_dir;
use rusqlite::Connection;
use crate::fs::*;
pub fn create_db() {
let path = app_data_dir();
let db_file = path.join("data.db");
let db_file = db_path();
if !db_file.exists() {
std::fs::File::create(&db_file).unwrap();
let connection = Connection::open(&db_file).unwrap();
@@ -51,8 +50,7 @@ fn make_card_stats(conn: &Connection) -> Result<(), rusqlite::Error> {
}
pub fn default_connection() -> Connection {
let path = app_data_dir();
let db_file = path.join("data.db");
let db_file = db_path();
let connection = Connection::open(db_file).unwrap();
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
connection
+3 -6
View File
@@ -1,17 +1,14 @@
use crate::dictionary::app_data_dir;
use reqwest::Client;
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::BufReader;
use crate::fs::*;
use crate::notification::NotificationData;
use crate::notification::NotificationData::*;
pub async fn get_voice(text: &str) -> Result<BufReader<File>, NotificationData> {
let mut path = app_data_dir();
path.push("voice");
if !path.exists() {
std::fs::create_dir(path.clone()).map_err(|_| PermissionDenied)?;
}
let mut path = voice_dir()?;
let hash = format!("{}.wav", hex::encode(Sha256::digest(text.as_bytes())));
path.push(hash);
if !path.exists() {
+4 -5
View File
@@ -1,9 +1,9 @@
use crate::dictionary::app_data_dir;
use std::io;
use log::info;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use zstd::{DEFAULT_COMPRESSION_LEVEL, Decoder, Encoder};
use crate::fs::*;
use crate::notification::NotificationData;
use crate::notification::NotificationData::{PermissionDenied, WebApiUnavailable};
@@ -11,8 +11,7 @@ use crate::notification::NotificationData::{PermissionDenied, WebApiUnavailable}
const API_URL: &str = "https://learning.micialware.ru/";
pub async fn send_data(id: String) -> Result<(), NotificationData> {
let path = app_data_dir();
let db_file = path.join("data.db");
let db_file = db_path();
let data = tokio::fs::read(db_file).await.map_err(|_| WebApiUnavailable)?;
let data = compress(data);
@@ -102,7 +101,7 @@ pub async fn get_web_version(key: &str) -> Result<u32, NotificationData> {
}
pub async fn get_local_version() -> u32 {
let file = app_data_dir().join("data.meta");
let file = db_meta_path();
if file.exists() {
let mut data = String::new();
File::open(file.clone())
@@ -126,7 +125,7 @@ pub async fn get_local_version() -> u32 {
}
pub async fn set_local_version(version: u32) {
let file_path = app_data_dir().join("data.meta");
let file_path = db_meta_path();
let mut file = OpenOptions::new()
.write(true)
.create(true)
+45
View File
@@ -0,0 +1,45 @@
use std::fs;
use std::path::PathBuf;
use crate::notification::NotificationData;
use crate::notification::NotificationData::PermissionDenied;
pub fn app_data_dir() -> PathBuf {
let mut dir = dirs::data_dir().unwrap();
dir.push("jap_learn");
if !dir.exists() {
fs::create_dir_all(dir.clone()).unwrap();
}
dir
}
pub fn db_path() -> PathBuf {
app_data_dir().join("data.db")
}
pub fn temp_db_path() -> PathBuf {
app_data_dir().join("data.db.tmp")
}
pub fn temp_import_path() -> PathBuf {
app_data_dir().join("import")
}
pub fn db_meta_path() -> PathBuf {
app_data_dir().join("data.meta")
}
pub fn history_dir() -> Result<PathBuf, NotificationData> {
let directory = app_data_dir().join("history");
if !directory.exists() {
fs::create_dir_all(directory.clone()).map_err(|_| PermissionDenied)?;
}
Ok(directory)
}
pub fn voice_dir() -> Result<PathBuf, NotificationData> {
let directory = app_data_dir().join("voice");
if !directory.exists() {
fs::create_dir_all(directory.clone()).map_err(|_| PermissionDenied)?;
}
Ok(directory)
}
+2 -202
View File
@@ -83,207 +83,7 @@ impl Id {
}
}
#[derive(Clone, Debug)]
pub struct KanaSet {
name: String,
pub(crate) chars_type: KanaType,
pub(crate) dictionary: Vec<Vec<(String, String)>>,
pub(crate) include_map: [bool; 10],
}
#[derive(Clone, Debug)]
pub enum KanaType {
Hiragana,
Katakana,
}
impl KanaSet {
pub fn hiragana() -> Self {
Self {
name: "Хирагана".to_string(),
chars_type: KanaType::Hiragana,
dictionary: vec![
vec![
(String::from("ぁ"), String::from("a")),
(String::from("ぃ"), String::from("i")),
(String::from("ぅ"), String::from("u")),
(String::from("ぇ"), String::from("e")),
(String::from("ぉ"), String::from("o")),
],
vec![
(String::from("さ"), String::from("sa")),
(String::from("し"), String::from("shi")),
(String::from("す"), String::from("su")),
(String::from("せ"), String::from("se")),
(String::from("そ"), String::from("so")),
],
vec![
(String::from("か"), String::from("ka")),
(String::from("き"), String::from("ki")),
(String::from("く"), String::from("ku")),
(String::from("け"), String::from("ke")),
(String::from("こ"), String::from("ko")),
],
// Ряд «та» (たちつてと)
vec![
(String::from("た"), String::from("ta")),
(String::from("ち"), String::from("chi")),
(String::from("つ"), String::from("tsu")),
(String::from("て"), String::from("te")),
(String::from("と"), String::from("to")),
],
// Ряд «на» (なにぬねの)
vec![
(String::from("な"), String::from("na")),
(String::from("に"), String::from("ni")),
(String::from("ぬ"), String::from("nu")),
(String::from("ね"), String::from("ne")),
(String::from("の"), String::from("no")),
],
// Ряд «ха» (はひふへほ)
vec![
(String::from("は"), String::from("ha")),
(String::from("ひ"), String::from("hi")),
(String::from("ふ"), String::from("fu")),
(String::from("へ"), String::from("he")),
(String::from("ほ"), String::from("ho")),
],
// Ряд «ма» (まみむめも)
vec![
(String::from("ま"), String::from("ma")),
(String::from("み"), String::from("mi")),
(String::from("む"), String::from("mu")),
(String::from("め"), String::from("me")),
(String::from("も"), String::from("mo")),
],
// Ряд «я» (やゆよ) — только 3 символа
vec![
(String::from("や"), String::from("ya")),
(String::from("ゆ"), String::from("yu")),
(String::from("よ"), String::from("yo")),
],
// Ряд «ра» (らりるれろ)
vec![
(String::from("ら"), String::from("ra")),
(String::from("り"), String::from("ri")),
(String::from("る"), String::from("ru")),
(String::from("れ"), String::from("re")),
(String::from("ろ"), String::from("ro")),
],
vec![
(String::from("わ"), String::from("wa")),
(String::from("を"), String::from("wo")),
(String::from("ん"), String::from("n")),
],
],
include_map: [true; 10],
}
}
pub fn katakana() -> Self {
Self {
name: "Катакана".to_string(),
chars_type: KanaType::Katakana,
dictionary: vec![
vec![
(String::from("ァ"), String::from("a")),
(String::from("ィ"), String::from("i")),
(String::from("ゥ"), String::from("u")),
(String::from("ェ"), String::from("e")),
(String::from("ォ"), String::from("o")),
],
vec![
(String::from("サ"), String::from("sa")),
(String::from("シ"), String::from("shi")),
(String::from("ス"), String::from("su")),
(String::from("セ"), String::from("se")),
(String::from("ソ"), String::from("so")),
],
vec![
(String::from("カ"), String::from("ka")),
(String::from("キ"), String::from("ki")),
(String::from("ク"), String::from("ku")),
(String::from("ケ"), String::from("ke")),
(String::from("コ"), String::from("ko")),
],
// Ряд «та» (タチツテト)
vec![
(String::from("タ"), String::from("ta")),
(String::from("チ"), String::from("chi")),
(String::from("ツ"), String::from("tsu")),
(String::from("テ"), String::from("te")),
(String::from("ト"), String::from("to")),
],
// Ряд «на» (ナニヌネノ)
vec![
(String::from("ナ"), String::from("na")),
(String::from("ニ"), String::from("ni")),
(String::from("ヌ"), String::from("nu")),
(String::from("ネ"), String::from("ne")),
(String::from("ノ"), String::from("no")),
],
// Ряд «ха» (ハヒフヘホ)
vec![
(String::from("ハ"), String::from("ha")),
(String::from("ヒ"), String::from("hi")),
(String::from("フ"), String::from("fu")),
(String::from("ヘ"), String::from("he")),
(String::from("ホ"), String::from("ho")),
],
// Ряд «ма» (マミムメモ)
vec![
(String::from("マ"), String::from("ma")),
(String::from("ミ"), String::from("mi")),
(String::from("ム"), String::from("mu")),
(String::from("メ"), String::from("me")),
(String::from("モ"), String::from("mo")),
],
// Ряд «я» (ヤユヨ) — только 3 символа
vec![
(String::from("ヤ"), String::from("ya")),
(String::from("ユ"), String::from("yu")),
(String::from("ヨ"), String::from("yo")),
],
// Ряд «ра» (ラリルレロ)
vec![
(String::from("ラ"), String::from("ra")),
(String::from("リ"), String::from("ri")),
(String::from("ル"), String::from("ru")),
(String::from("レ"), String::from("re")),
(String::from("ロ"), String::from("ro")),
],
vec![
(String::from("ワ"), String::from("wa")),
(String::from("ヲ"), String::from("wo")),
(String::from("ン"), String::from("n")),
],
],
include_map: [true; 10],
}
}
pub fn list(&self) -> Vec<(String, String)> {
let mut current_set: Vec<(String, String)> = Vec::new();
for i in 0..10 {
if self.include_map[i] {
self.dictionary[i].iter().for_each(|v| {
current_set.push(v.clone());
});
}
}
current_set
}
}
impl Default for KanaSet {
fn default() -> Self {
KanaSet::hiragana()
}
}
impl PartialEq<Self> for KanaSet {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
#[derive(Clone, Debug)]
pub struct WordData {
pub id: Id,
@@ -448,7 +248,7 @@ impl DeckData {
}
}
update_stat_score(word, &self.state.lock().connection);
push_note(
let _ = push_note(
self.settings.id,
HistoryItem {
timestamp: Utc::now(),
@@ -457,7 +257,7 @@ impl DeckData {
before: old_score,
after: new_score,
},
)
);
}
pub fn len(&self) -> usize {
self.set.len()
+6 -18
View File
@@ -1,26 +1,14 @@
#![windows_subsystem = "windows"]
mod data_provider;
mod dictionary;
mod dictionary_quiz;
mod history;
pub mod import;
mod lang;
pub mod navigation;
mod quiz;
mod randomizer;
mod repetition;
mod repetition_settings;
mod repetitions;
mod selector;
pub mod styling;
mod sync;
mod word;
mod writing;
pub mod notification;
pub mod app;
mod lang;
mod styling;
mod pages;
mod navigation;
mod fs;
mod core;
use crate::app::start;
use crate::navigation::RootMessage;
use mimalloc::MiMalloc;
#[global_allocator]
+41 -34
View File
@@ -1,39 +1,46 @@
use crate::app::{fill_state, AppState};
use crate::data_provider::history::{get_history_of_set, history_dir};
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::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
use crate::dictionary_quiz::{DictionaryQuizMessage, DictionaryQuizState};
use crate::history::{HistoryMessage, HistoryState};
use crate::import::{ImportMessage, ImportState};
use crate::data_provider::web_api::{
get_local_version, get_web_version, load_data, set_local_version,
};
use crate::fs::*;
use crate::lang::Id;
use crate::message_navigation;
use crate::navigation::Page::*;
use crate::navigation::RootMessage::{DataLoaded, Keyboard, Notification, PushNotification, UpdateData};
use crate::notification::{NotificationData, NotificationManager, NotificationMessage, NotificationRequest};
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::navigation::RootMessage::{
DataLoaded, Keyboard, Notification, PushNotification, UpdateData,
};
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::sync::{SyncMessage, SyncState};
use crate::view_navigation;
use crate::word::{WordMessage, WordState};
use crate::writing::{WritingMessage, WritingState};
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;
use log::info;
impl Default for ScreenState {
fn default() -> Self {
@@ -91,7 +98,7 @@ pub enum Page {
pub struct ScreenState {
stack: Vec<Page>,
app_state: Arc<Mutex<AppState>>,
notification_manager: NotificationManager
notification_manager: NotificationManager,
}
impl ScreenState {
@@ -108,10 +115,7 @@ impl ScreenState {
let key = state_guard.sync_data.key.clone().unwrap();
final_task = Task::batch([
reading_additional_task,
Task::perform(
Self::load_web_backup(key),
|result| {
match result {
Task::perform(Self::load_web_backup(key), |result| match result {
Ok(update) => {
if update {
UpdateData
@@ -119,10 +123,8 @@ impl ScreenState {
RootMessage::None
}
}
Err(err) => PushNotification(NotificationRequest::Warning(err))
}
}
),
Err(err) => PushNotification(NotificationRequest::Warning(err)),
}),
]);
} else {
final_task = reading_additional_task;
@@ -135,6 +137,7 @@ impl ScreenState {
async fn load_additional_data() -> RootMessage {
let directory = history_dir();
if let Ok(directory) = directory {
let mut map = HashMap::new();
for file in directory.read_dir().unwrap().flatten() {
let mut vec = vec![];
@@ -148,6 +151,7 @@ impl ScreenState {
.unwrap();
let history = get_history_of_set(id);
if let Ok(history) = history {
let by_date = history.chunk_by(|x, x1| {
x.timestamp.naive_local().date() == x1.timestamp.naive_local().date()
});
@@ -155,8 +159,13 @@ impl ScreenState {
vec.push((group[0].timestamp.naive_local().date(), group.len() as u32));
}
map.insert(id, vec);
} else {
return PushNotification(NotificationRequest::Warning(history.err().unwrap()));
}
DataLoaded(map)
}
return DataLoaded(map);
}
PushNotification(NotificationRequest::Warning(directory.err().unwrap()))
}
async fn load_web_backup(string: String) -> Result<bool, NotificationData> {
@@ -191,9 +200,8 @@ impl ScreenState {
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");
let db_file = db_path();
let temp_db_file = temp_db_path();
std::fs::rename(temp_db_file, &db_file).unwrap();
state.connection = default_connection();
@@ -206,7 +214,7 @@ impl ScreenState {
}
if let PushNotification(notification) = &message {
return self.notification_manager.push(notification.clone())
return self.notification_manager.push(notification.clone());
}
let task: (Task<RootMessage>, bool) = self.navigate_or_update(message);
@@ -255,7 +263,6 @@ impl ScreenState {
}
pub fn view(&self) -> Element<'_, RootMessage> {
info!("View update");
let time = Instant::now();
let view = view_navigation!(
@@ -275,7 +282,7 @@ impl ScreenState {
Import
);
let notifications = self.notification_manager.draw().map(RootMessage::Notification);
let notifications = self.notification_manager.draw().map(Notification);
info!("View took {:?}", time.elapsed());
stack![view, notifications].width(Fill).height(Fill).into()
+13
View File
@@ -0,0 +1,13 @@
pub mod dictionary;
pub mod dictionary_quiz;
pub mod history;
pub mod import;
pub mod quiz;
pub mod randomizer;
pub mod repetition;
pub mod repetition_settings;
pub mod repetitions;
pub mod selector;
pub mod sync;
pub mod word;
pub mod writing;
+4 -12
View File
@@ -1,12 +1,12 @@
use crate::data_provider::words::{delete_group, delete_word, update_group, update_word};
use crate::dictionary::DictionaryMessage::*;
use crate::dictionary_quiz::DictionaryQuizState;
use crate::import::ImportState;
use crate::pages::dictionary::DictionaryMessage::*;
use crate::pages::dictionary_quiz::DictionaryQuizState;
use crate::pages::import::ImportState;
use crate::lang::{WordData, WordGroup};
use crate::navigation::Page::{Import, Word};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*;
use crate::word::WordState;
use crate::pages::word::WordState;
use chrono::{DateTime, TimeDelta, Utc};
use hashbrown::{HashMap, HashSet};
use iced::alignment::Vertical::Center;
@@ -619,15 +619,7 @@ pub fn split_with_coma(ts: &str) -> Vec<String> {
.collect::<Vec<String>>()
}
pub fn app_data_dir() -> PathBuf {
let mut dir = dirs::data_dir().unwrap();
dir.push("jap_learn");
if !dir.exists() {
fs::create_dir_all(dir.clone()).unwrap();
}
dir
}
#[derive(Hash, PartialEq, Eq)]
struct WordLineState {
@@ -1,10 +1,10 @@
use crate::RootMessage;
use crate::dictionary::split_with_coma;
use crate::dictionary_quiz::DictionaryQuizMessage::*;
use crate::navigation::RootMessage;
use crate::pages::dictionary::split_with_coma;
use crate::pages::dictionary_quiz::DictionaryQuizMessage::*;
use crate::lang::WordData;
use crate::navigation::Page::PreviousPage;
use crate::navigation::*;
use crate::quiz::Score;
use crate::pages::quiz::Score;
use crate::styling::*;
use iced::Background::Color;
use iced::border::Radius;
+1 -1
View File
@@ -49,7 +49,7 @@ impl NavigatedPage<HistoryMessage> for HistoryState {
impl HistoryState {
pub fn new(id: crate::lang::Id, state: Arc<Mutex<AppState>>) -> Self {
let state = state.lock();
let history = get_history_of_set(id);
let history = get_history_of_set(id).unwrap_or_default();
let words = history
.iter()
.map(
+5 -5
View File
@@ -1,7 +1,6 @@
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::pages::import::ImportMessage::*;
use crate::lang::{WordData, WordGroup};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::NotificationData::OpenFileError;
@@ -28,6 +27,7 @@ use log::info;
use tokio::task::spawn_blocking;
use zip::ZipArchive;
use crate::app::AppState;
use crate::fs::*;
const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];
@@ -376,7 +376,7 @@ impl ImportState {
let file = File::open(path).map_err(|_| OpenFileError)?;
let reader = BufReader::new(file);
let mut archive = ZipArchive::new(reader).map_err(|_| OpenFileError)?;
let temp_file_path = app_data_dir().join("import");
let temp_file_path = temp_import_path();
let mut temp_file = OpenOptions::new()
.write(true)
.create(true)
@@ -394,7 +394,7 @@ impl ImportState {
}
}
fn read_import_file() -> Result<ImportData, NotificationData> {
let temp_file_path = app_data_dir().join("import");
let temp_file_path = temp_import_path();
let connection = Connection::open(&temp_file_path).map_err(|_| OpenFileError)?;
let data = load_groups(&connection)?;
Ok(data)
@@ -431,7 +431,7 @@ impl ImportState {
Task::perform(
async move {
spawn_blocking(move || {
let temp_file_path = app_data_dir().join("import");
let temp_file_path = temp_import_path();
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());
+3 -2
View File
@@ -1,12 +1,13 @@
use crate::lang::KanaSet;
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::quiz::QuizMessage::*;
use crate::pages::quiz::QuizMessage::*;
use crate::styling::*;
use iced::widget::*;
use iced::{Element, Fill, Task, alignment};
use rand::seq::SliceRandom;
use crate::app::USER_FONT;
use crate::core::kana_set::KanaSet;
#[derive(Clone, Debug)]
pub struct QuizState {
@@ -1,6 +1,6 @@
use crate::RootMessage;
use crate::navigation::RootMessage;
use crate::navigation::{NavigatedPage, Page};
use crate::randomizer::RandomizerMessage::{Back, Edit, Start};
use crate::pages::randomizer::RandomizerMessage::{Back, Edit, Start};
use crate::styling::{HALF_SPACING, back_overlay, jl_button};
use iced::Task;
use iced::widget::{button, text_editor};
File renamed without changes.
@@ -4,7 +4,7 @@ use crate::data_provider::card_stats::{delete_stats, load_stats_of_deck};
use crate::lang::DeckSettings;
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::repetition_settings::RepetitionSettingsMessage::*;
use crate::pages::repetition_settings::RepetitionSettingsMessage::*;
use crate::styling::*;
use iced::widget::button::danger;
use iced::widget::text_input::Catalog;
@@ -1,12 +1,12 @@
use crate::data_provider::card_sets::update_deck;
use crate::data_provider::card_stats::{add_stat_list, load_stats_of_deck};
use crate::history::HistoryState;
use crate::pages::history::HistoryState;
use crate::lang::{AppendMode, CardStatistics, DeckSettings, Id, OrderMode};
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::repetition::RepetitionState;
use crate::repetition_settings::RepetitionSettingsState;
use crate::repetitions::RepetitionsMessage::*;
use crate::pages::repetition::RepetitionState;
use crate::pages::repetition_settings::RepetitionSettingsState;
use crate::pages::repetitions::RepetitionsMessage::*;
use crate::styling::*;
use chrono::{Days, Local, Utc};
use hashbrown::{HashMap, HashSet};
+8 -8
View File
@@ -1,13 +1,12 @@
use crate::dictionary::DictionaryState;
use crate::lang::{KanaSet, KanaType};
use crate::pages::dictionary::DictionaryState;
use crate::navigation::Page::*;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::randomizer::RandomizerState;
use crate::repetitions::RepetitionsState;
use crate::selector::SelectorMessage::ChangeMode;
use crate::pages::randomizer::RandomizerState;
use crate::pages::repetitions::RepetitionsState;
use crate::pages::selector::SelectorMessage::ChangeMode;
use crate::styling::*;
use crate::sync::SyncState;
use crate::writing::WritingState;
use crate::pages::sync::SyncState;
use crate::pages::writing::WritingState;
use iced::widget::button::{Status, Style};
use iced::widget::*;
use iced::{Element, Task, alignment};
@@ -17,7 +16,8 @@ use iced_core::Color;
use parking_lot::Mutex;
use std::sync::Arc;
use crate::app::AppState;
use crate::quiz::QuizState;
use crate::core::kana_set::{KanaSet, KanaType};
use crate::pages::quiz::QuizState;
pub struct SelectorState {
pub set: KanaSet,
+1 -1
View File
@@ -3,7 +3,7 @@ use crate::data_provider::web_api::*;
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*;
use crate::sync::SyncMessage::*;
use crate::pages::sync::SyncMessage::*;
use iced::widget::button::danger;
use iced::widget::container::rounded_box;
use iced::widget::{button, column, container, progress_bar, row, space, text, toggler};
+1 -1
View File
@@ -3,7 +3,7 @@ use crate::lang::WordData;
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*;
use crate::word::WordMessage::*;
use crate::pages::word::WordMessage::*;
use iced::widget::button::danger;
use iced::widget::{button, column, row, rule, scrollable, text, text_input};
use iced::{Element, Fill, Task};
+3 -3
View File
@@ -1,11 +1,11 @@
use crate::RootMessage;
use crate::lang::KanaSet;
use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*;
use iced::widget::*;
use iced::{Element, Fill, Task, alignment};
use rand::seq::SliceRandom;
use crate::core::kana_set::KanaSet;
#[derive(Clone, Debug)]
pub struct WritingState {
-1
View File
@@ -1,4 +1,3 @@
use crate::repetitions::Style;
use iced::Element;
use iced::border::Radius;
use iced::widget::button::*;