Add navigation to repetition page
This commit is contained in:
Generated
+114
@@ -441,6 +441,19 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
@@ -826,6 +839,18 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -1201,6 +1226,15 @@ dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
|
||||
dependencies = [
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -1225,6 +1259,30 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.14.0"
|
||||
@@ -1437,10 +1495,12 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
name = "jap_learn"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs",
|
||||
"iced",
|
||||
"rand",
|
||||
"rhai",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1553,6 +1613,16 @@ dependencies = [
|
||||
"redox_syscall 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lilt"
|
||||
version = "0.8.1"
|
||||
@@ -2497,6 +2567,32 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rsqlite-vfs"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
|
||||
dependencies = [
|
||||
"hashbrown 0.16.1",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"chrono",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
"sqlite-wasm-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
@@ -2811,6 +2907,18 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlite-wasm-rs"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"js-sys",
|
||||
"rsqlite-vfs",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -3125,6 +3233,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
||||
@@ -10,3 +10,5 @@ dirs = "6.0.0"
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
rhai = "1.24.0"
|
||||
chrono = "0.4.44"
|
||||
rusqlite = { version = "0.39.0", features = ["chrono"] }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::collections::HashMap;
|
||||
use iced::time::now;
|
||||
use rusqlite::Connection;
|
||||
use crate::lang::{CardSet, DictionaryElement};
|
||||
use crate::repetitions::CardSetSettings;
|
||||
|
||||
pub fn load_sets(connection: &Connection) -> Vec<CardSetSettings> {
|
||||
let mut stmt = connection.prepare("SELECT id, name, forward, backward, filter FROM card_set").unwrap();
|
||||
let iter = stmt.query_map([], |row| {
|
||||
Ok(CardSetSettings {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
forward: row.get(2)?,
|
||||
backward: row.get(3)?,
|
||||
filter: row.get(4)?,
|
||||
count: None
|
||||
})
|
||||
}).unwrap();
|
||||
|
||||
let mut buffer = vec![];
|
||||
for word in iter {
|
||||
buffer.push(word.unwrap());
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn add_set(set: &mut CardSetSettings, connection: &Connection) {
|
||||
let index = connection
|
||||
.query_row(
|
||||
"INSERT INTO card_set (name, forward, backward, filter) VALUES (?1, ?2, ?3, ?4) RETURNING id",
|
||||
(
|
||||
&set.name,
|
||||
&set.forward,
|
||||
&set.backward,
|
||||
&set.filter,
|
||||
),
|
||||
|row| row.get(0)
|
||||
)
|
||||
.unwrap_or_else(|e| {println!("{}", e); 0});
|
||||
|
||||
set.id = index;
|
||||
}
|
||||
|
||||
pub fn update_card_set(set: &mut CardSetSettings, connection: &Connection){
|
||||
if set.id == 0 {
|
||||
add_set(set, &connection);
|
||||
}
|
||||
|
||||
else {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE card_set SET name = ?1, forward = ?2, backward = ?3, filter = ?4 WHERE id = ?5",
|
||||
(
|
||||
&set.name,
|
||||
&set.forward,
|
||||
&set.backward,
|
||||
&set.filter,
|
||||
&set.id
|
||||
),
|
||||
)
|
||||
.unwrap_or_else(|e| {println!("{}", e); 0});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_set(set: &CardSetSettings, connection: &Connection) {
|
||||
if set.id == 0 {
|
||||
return;
|
||||
}
|
||||
connection
|
||||
.execute("DELETE FROM card_set WHERE id = ?1", (&set.id,))
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::collections::HashMap;
|
||||
use chrono::{DateTime, Utc};
|
||||
use iced::time::now;
|
||||
use rusqlite::{Connection, ToSql};
|
||||
use rusqlite::types::ToSqlOutput;
|
||||
use crate::lang::{CardSet, CardStatistics, DictionaryElement};
|
||||
use crate::repetitions::CardSetSettings;
|
||||
|
||||
pub fn load_stats_of_set(set: &CardSetSettings, connection: &Connection) -> Vec<CardStatistics> {
|
||||
let mut stmt = connection.prepare("SELECT id, word_id, score, last_opened FROM card_stats WHERE set_id = ?1").unwrap();
|
||||
let iter = stmt.query_map((set.id,), |row| {
|
||||
Ok(CardStatistics {
|
||||
id: row.get(0)?,
|
||||
word_id: row.get(1)?,
|
||||
score: row.get(2)?,
|
||||
last_open: row.get(2)?,
|
||||
})
|
||||
}).unwrap();
|
||||
|
||||
let mut buffer = vec![];
|
||||
for word in iter {
|
||||
buffer.push(word.unwrap());
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn add_set(set: &mut CardSetSettings, connection: &Connection) {
|
||||
let index = connection
|
||||
.query_row(
|
||||
"INSERT INTO card_set (name, forward, backward, filter) VALUES (?1, ?2, ?3, ?4) RETURNING id",
|
||||
(
|
||||
&set.name,
|
||||
&set.forward,
|
||||
&set.backward,
|
||||
&set.filter,
|
||||
),
|
||||
|row| row.get(0)
|
||||
)
|
||||
.unwrap_or_else(|e| {println!("{}", e); 0});
|
||||
|
||||
set.id = index;
|
||||
}
|
||||
|
||||
pub fn update_card_set(set: &mut CardSetSettings, connection: &Connection){
|
||||
if set.id == 0 {
|
||||
add_set(set, &connection);
|
||||
}
|
||||
|
||||
else {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE card_set SET name = ?1, forward = ?2, backward = ?3, filter = ?4 WHERE id = ?5",
|
||||
(
|
||||
&set.name,
|
||||
&set.forward,
|
||||
&set.backward,
|
||||
&set.filter,
|
||||
&set.id
|
||||
),
|
||||
)
|
||||
.unwrap_or_else(|e| {println!("{}", e); 0});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_set(set: &CardSetSettings, connection: &Connection) {
|
||||
if set.id == 0 {
|
||||
return;
|
||||
}
|
||||
connection
|
||||
.execute("DELETE FROM card_set WHERE id = ?1", (&set.id,))
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub(crate) mod words;
|
||||
pub(crate) mod card_sets;
|
||||
pub(crate) mod card_stats;
|
||||
|
||||
use crate::lang;
|
||||
use crate::dictionary;
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
use crate::dictionary::app_data_dir;
|
||||
use crate::lang::DictionaryElement;
|
||||
use rusqlite::Connection;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn create_db() {
|
||||
let path = app_data_dir();
|
||||
let db_file = path.join("data.db");
|
||||
if !db_file.exists() {
|
||||
std::fs::File::create(&db_file).unwrap();
|
||||
let connection = Connection::open(&db_file).unwrap();
|
||||
create_words_table(&connection);
|
||||
create_card_stat_table(&connection);
|
||||
create_card_set_table(&connection);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_word(word: &mut DictionaryElement, connection: &Connection) {
|
||||
let index = connection
|
||||
.query_row(
|
||||
"INSERT INTO words (key, value, tags, more) VALUES (?1, ?2, ?3, ?4) RETURNING id",
|
||||
(
|
||||
&word.key,
|
||||
&word.value,
|
||||
&word.tags,
|
||||
serde_json::to_string(&word.additional).unwrap(),
|
||||
),
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
|
||||
word.id = index;
|
||||
}
|
||||
|
||||
pub fn update_word(word: &mut DictionaryElement, connection: &Connection) {
|
||||
if word.id == 0 {
|
||||
add_word(word, &connection);
|
||||
} else {
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE words SET key = ?1, value = ?2, tags = ?3, more = ?4 WHERE id = ?5",
|
||||
(
|
||||
&word.key,
|
||||
&word.value,
|
||||
&word.tags,
|
||||
serde_json::to_string(&word.additional).unwrap(),
|
||||
&word.id,
|
||||
),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_word(word: &DictionaryElement, connection: &Connection) {
|
||||
if word.id == 0 {
|
||||
return;
|
||||
}
|
||||
connection
|
||||
.execute("DELETE FROM words WHERE id = ?1", (&word.id,))
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
pub fn load_words(connection: &Connection) -> Vec<DictionaryElement> {
|
||||
let mut stmt = connection
|
||||
.prepare("SELECT id, key, value, tags, more FROM words")
|
||||
.unwrap();
|
||||
let word_iter = stmt
|
||||
.query_map([], |row| {
|
||||
let addinionals: String = row.get(4)?;
|
||||
Ok(DictionaryElement {
|
||||
id: row.get(0)?,
|
||||
key: row.get(1)?,
|
||||
value: row.get(2)?,
|
||||
tags: row.get(3)?,
|
||||
additional: serde_json::from_str::<HashMap<String, String>>(&addinionals).unwrap(),
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut buffer = vec![];
|
||||
for word in word_iter {
|
||||
buffer.push(word.unwrap());
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
fn create_words_table(conn: &Connection) {
|
||||
conn.execute(
|
||||
"CREATE TABLE words (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
tags TEXT NOT NULL,
|
||||
more TEXT
|
||||
)",
|
||||
(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
fn create_card_stat_table(conn: &Connection) {
|
||||
conn.execute(
|
||||
"CREATE TABLE card_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
word_id INTEGER NOT NULL,
|
||||
set_id TEXT NOT NULL,
|
||||
score INTEGER NOT NULL DEFAULT 1,
|
||||
last_opened INTEGER NOT NULL,
|
||||
FOREIGN KEY (word_id) REFERENCES words (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (set_id) REFERENCES sets (id) ON DELETE CASCADE
|
||||
)",
|
||||
(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
fn create_card_set_table(conn: &Connection) {
|
||||
conn.execute(
|
||||
"CREATE TABLE card_set (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
forward TEXT NOT NULL,
|
||||
backward TEXT NOT NULL,
|
||||
filter TEXT NOT NULL
|
||||
)",
|
||||
(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Qwerty {}
|
||||
+38
-28
@@ -1,11 +1,12 @@
|
||||
use crate::data_provider::words::{delete_word, update_word};
|
||||
use crate::dictionary::DictionaryMessage::Test;
|
||||
use crate::dictionary_test::DictionaryQuizState;
|
||||
use crate::lang::DictionaryElement;
|
||||
use crate::{AppState, NavigatedPage, Page, RootMessage};
|
||||
use iced::alignment::Vertical::Center;
|
||||
use iced::widget::button::Style;
|
||||
use iced::widget::*;
|
||||
use iced::{Border, Color, Length, Shadow, Task};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
@@ -22,31 +23,13 @@ pub struct DictionaryState {
|
||||
no_typing: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DictionaryElement {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub tags: String,
|
||||
pub additional: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl DictionaryElement {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
key: String::new(),
|
||||
value: String::new(),
|
||||
tags: String::new(),
|
||||
additional: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DictionaryMessage {
|
||||
Back,
|
||||
SetTags(usize, String),
|
||||
SetKey(usize, String),
|
||||
SetValue(usize, String),
|
||||
SubmitWord(usize),
|
||||
Remove(usize),
|
||||
NewWord,
|
||||
Include(usize, bool),
|
||||
@@ -109,6 +92,7 @@ impl DictionaryState {
|
||||
dict.push(DictionaryElement::new());
|
||||
self.include_map.push(false);
|
||||
}
|
||||
|
||||
DictionaryMessage::SetKey(i, v) => {
|
||||
let dict = &mut self.state.lock().unwrap().dictionary;
|
||||
dict[i].key = v
|
||||
@@ -116,7 +100,7 @@ impl DictionaryState {
|
||||
DictionaryMessage::SetValue(i, v) => {
|
||||
let dict = &mut self.state.lock().unwrap().dictionary;
|
||||
dict[i].value = v
|
||||
},
|
||||
}
|
||||
DictionaryMessage::SetTags(i, v) => {
|
||||
{
|
||||
let dict = &mut self.state.lock().unwrap().dictionary;
|
||||
@@ -125,32 +109,45 @@ impl DictionaryState {
|
||||
|
||||
self.update_tags();
|
||||
}
|
||||
|
||||
DictionaryMessage::Remove(i) => {
|
||||
let dict = &mut self.state.lock().unwrap().dictionary;
|
||||
dict.remove(i);
|
||||
let state = &mut self.state.lock().unwrap();
|
||||
let dict = &mut state.dictionary;
|
||||
let word = dict.remove(i);
|
||||
delete_word(&word, &state.connection)
|
||||
}
|
||||
DictionaryMessage::Include(i, b) => self.include_map[i] = b,
|
||||
DictionaryMessage::IncludeTag(t, v) => {
|
||||
self.tag_map.insert(t, v);
|
||||
self.update_words_include()
|
||||
}
|
||||
|
||||
DictionaryMessage::ResetTags => {
|
||||
self.tag_map.iter_mut().for_each(|(_, v)| *v = false);
|
||||
self.include_map.iter_mut().for_each(|x| *x = false)
|
||||
}
|
||||
|
||||
DictionaryMessage::Save => {
|
||||
let dir = dict_file();
|
||||
/* let dir = dict_file();
|
||||
let dict = &self.state.lock().unwrap().dictionary;
|
||||
|
||||
let content = serde_json::to_string_pretty(&dict.clone()).unwrap();
|
||||
fs::write(dir, content.clone())
|
||||
.unwrap_or_else(|e| println!("Can't write file: {}", e));
|
||||
.unwrap_or_else(|e| println!("Can't write file: {}", e));*/
|
||||
}
|
||||
DictionaryMessage::SetReverse(v) => self.reverse = v,
|
||||
DictionaryMessage::Search(s) => {
|
||||
self.search = s;
|
||||
}
|
||||
DictionaryMessage::SetTyping(b) => self.no_typing = b,
|
||||
DictionaryMessage::SubmitWord(i) => {
|
||||
let state = &mut self.state.lock().unwrap();
|
||||
let connection = &state.connection;
|
||||
let word = &mut state.dictionary.get(i).unwrap().clone();
|
||||
|
||||
update_word(word, &connection);
|
||||
state.dictionary[i] = word.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -206,19 +203,22 @@ impl DictionaryState {
|
||||
text_input("Ключ", &word.key)
|
||||
.size(20)
|
||||
.width(Length::Fill)
|
||||
.on_input(move |string| DictionaryMessage::SetKey(i, string)),
|
||||
.on_input(move |string| DictionaryMessage::SetKey(i, string))
|
||||
.on_submit(DictionaryMessage::SubmitWord(i)),
|
||||
);
|
||||
line = line.push(
|
||||
text_input("Значение", &word.value)
|
||||
.size(20)
|
||||
.width(Length::Fill)
|
||||
.on_input(move |string| DictionaryMessage::SetValue(i, string)),
|
||||
.on_input(move |string| DictionaryMessage::SetValue(i, string))
|
||||
.on_submit(DictionaryMessage::SubmitWord(i)),
|
||||
);
|
||||
line = line.push(
|
||||
text_input("Тэги", &word.tags)
|
||||
.size(20)
|
||||
.width(Length::Fill)
|
||||
.on_input(move |string| DictionaryMessage::SetTags(i, string)),
|
||||
.on_input(move |string| DictionaryMessage::SetTags(i, string))
|
||||
.on_submit(DictionaryMessage::SubmitWord(i)),
|
||||
);
|
||||
|
||||
line = line
|
||||
@@ -364,3 +364,13 @@ pub fn dict_file() -> PathBuf {
|
||||
dir.push("dict.json");
|
||||
dir
|
||||
}
|
||||
|
||||
pub fn app_data_dir() -> PathBuf {
|
||||
let mut dir = dirs::data_dir().unwrap();
|
||||
dir.push("jap_learn");
|
||||
if !dir.exists() {
|
||||
fs::create_dir(dir.clone()).unwrap();
|
||||
}
|
||||
|
||||
dir
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::dictionary::{split_with_coma, DictionaryElement};
|
||||
use crate::dictionary::{split_with_coma};
|
||||
use crate::quiz::Score;
|
||||
use crate::Page::PreviousPage;
|
||||
use crate::RootMessage;
|
||||
@@ -9,6 +9,8 @@ use iced::widget::{button, container, row, space, text, text_input, Row};
|
||||
use iced::Background::Color;
|
||||
use iced::{alignment, Border, Element, Fill, Task, Theme};
|
||||
use rand::prelude::SliceRandom;
|
||||
use crate::lang::DictionaryElement;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DictionaryQuizState {
|
||||
words: Vec<DictionaryElement>,
|
||||
|
||||
+102
-1
@@ -1,3 +1,13 @@
|
||||
use crate::repetitions::CardSetSettings;
|
||||
use crate::AppState;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::data_provider::card_stats::load_stats_of_set;
|
||||
|
||||
const MAX_SCORE: u8 = 25_u8;
|
||||
const FADE_PER_DAY: f32 = 0.95;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KanaSet {
|
||||
name: String,
|
||||
@@ -177,7 +187,7 @@ impl KanaSet {
|
||||
}
|
||||
}
|
||||
|
||||
/* pub fn next(&mut self) -> (String, String) {
|
||||
/* pub fn next(&mut self) -> (String, String) {
|
||||
let current_set = self.list();
|
||||
|
||||
let mut rand = rand::rng();
|
||||
@@ -210,3 +220,94 @@ impl PartialEq<Self> for KanaSet {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DictionaryElement {
|
||||
pub id: u32,
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub tags: String,
|
||||
pub additional: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl DictionaryElement {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
key: String::new(),
|
||||
value: String::new(),
|
||||
tags: String::new(),
|
||||
additional: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CardStatistics {
|
||||
pub id: u32,
|
||||
pub word_id: u32,
|
||||
pub last_open: DateTime<Utc>,
|
||||
pub score: u8,
|
||||
}
|
||||
|
||||
impl CardStatistics {
|
||||
pub fn open(&mut self, status: WordOpenMode) {
|
||||
self.last_open = Utc::now();
|
||||
match status {
|
||||
WordOpenMode::Easy => {
|
||||
self.score += 5;
|
||||
}
|
||||
WordOpenMode::Ok => {
|
||||
self.score += 3;
|
||||
}
|
||||
WordOpenMode::Hard => {
|
||||
self.score -= 1;
|
||||
}
|
||||
WordOpenMode::None => {
|
||||
self.score /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if self.score < 1 {
|
||||
self.score = 1
|
||||
} else if self.score > MAX_SCORE {
|
||||
self.score = MAX_SCORE
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculated_score(&self) -> f32 {
|
||||
let time = Utc::now() - self.last_open;
|
||||
let days = time.num_days();
|
||||
let multiplier = FADE_PER_DAY.powi(days as i32);
|
||||
self.score as f32 * multiplier
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WordOpenMode {
|
||||
Easy,
|
||||
Ok,
|
||||
Hard,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CardSet {
|
||||
set: Vec<CardStatistics>,
|
||||
last_weights: Vec<f32>,
|
||||
}
|
||||
|
||||
impl CardSet {
|
||||
pub fn new(settings: CardSetSettings, state: Arc<Mutex<AppState>>) -> Self {
|
||||
let state_locked = state.lock().unwrap();
|
||||
|
||||
let current_set = load_stats_of_set(&settings, &state_locked.connection);
|
||||
let last_list = settings.get_word_list(&state_locked);
|
||||
|
||||
let weights = current_set.iter().map(|x| x.calculated_score()).collect();
|
||||
|
||||
Self {
|
||||
set: current_set,
|
||||
last_weights: weights,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-17
@@ -8,15 +8,16 @@ mod repetition;
|
||||
mod repetitions;
|
||||
mod selector;
|
||||
mod writing;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use crate::dictionary::{DictionaryElement, DictionaryMessage, DictionaryState};
|
||||
mod data_provider;
|
||||
use crate::data_provider::card_sets::load_sets;
|
||||
use crate::data_provider::words::{create_db, load_words};
|
||||
use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
|
||||
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
|
||||
use crate::lang::DictionaryElement;
|
||||
use crate::quiz::*;
|
||||
use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState};
|
||||
use crate::repetition::{RepetitionMessage, RepetitionState};
|
||||
use crate::repetitions::{RepetitionsMessage, RepetitionsState};
|
||||
use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState};
|
||||
use crate::selector::*;
|
||||
use crate::writing::{WritingMessage, WritingState};
|
||||
use crate::Page::{
|
||||
@@ -25,6 +26,7 @@ use crate::Page::{
|
||||
use iced::widget::text;
|
||||
use iced::Element;
|
||||
use iced::{Font, Task};
|
||||
use rusqlite::Connection;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
fn main() -> iced::Result {
|
||||
@@ -32,7 +34,8 @@ fn main() -> iced::Result {
|
||||
.title("Kana learn app")
|
||||
.font(include_bytes!("../noto.ttf"))
|
||||
.default_font(Font::with_name("Noto Sans JP"))
|
||||
.run()
|
||||
.
|
||||
run()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -65,21 +68,19 @@ pub struct ScreenState {
|
||||
|
||||
pub struct AppState {
|
||||
pub dictionary: Vec<DictionaryElement>,
|
||||
pub card_sets: Vec<CardSetSettings>,
|
||||
pub connection: Connection
|
||||
}
|
||||
|
||||
impl Default for ScreenState {
|
||||
fn default() -> Self {
|
||||
let mut current_dict = "[]".to_string();
|
||||
match File::open(dictionary::dict_file()) {
|
||||
Ok(mut f) => {
|
||||
current_dict = String::new();
|
||||
f.read_to_string(&mut current_dict).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let list: Vec<DictionaryElement> = serde_json::from_str(¤t_dict).unwrap();
|
||||
let state = Arc::new(Mutex::new(AppState { dictionary: list }));
|
||||
let path = app_data_dir();
|
||||
let db_file = path.join("data.db");
|
||||
let connection = Connection::open(db_file).unwrap();
|
||||
let list: Vec<DictionaryElement> = load_words(&connection);
|
||||
let sets: Vec<CardSetSettings> = load_sets(&connection);
|
||||
|
||||
let state = Arc::new(Mutex::new(AppState { dictionary: list, card_sets: sets, connection }));
|
||||
ScreenState {
|
||||
stack: vec![Selector(SelectorState::new(state.clone()))],
|
||||
}
|
||||
@@ -88,6 +89,7 @@ impl Default for ScreenState {
|
||||
|
||||
impl ScreenState {
|
||||
pub fn boot() -> (ScreenState, Task<RootMessage>) {
|
||||
create_db();
|
||||
(ScreenState::default(), Task::none())
|
||||
}
|
||||
pub fn update(&mut self, message: RootMessage) -> Task<RootMessage> {
|
||||
|
||||
+10
-10
@@ -1,16 +1,15 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::Page::PreviousPage;
|
||||
use crate::{NavigatedPage, Page, RootMessage};
|
||||
use crate::{AppState, NavigatedPage, Page, RootMessage};
|
||||
use iced::widget::{button, container};
|
||||
use iced::{Element, Fill, Left, Task};
|
||||
use crate::lang::CardSet;
|
||||
use crate::repetitions::CardSetSettings;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone)]
|
||||
pub struct RepetitionState {
|
||||
}
|
||||
|
||||
impl Default for RepetitionState {
|
||||
fn default() -> Self {
|
||||
RepetitionState::new()
|
||||
}
|
||||
pub set: CardSetSettings,
|
||||
pub state: Arc<Mutex<AppState>>,
|
||||
}
|
||||
|
||||
impl NavigatedPage<RepetitionMessage> for RepetitionState {
|
||||
@@ -24,9 +23,10 @@ impl NavigatedPage<RepetitionMessage> for RepetitionState {
|
||||
}
|
||||
|
||||
impl RepetitionState {
|
||||
pub(crate) fn new() -> RepetitionState {
|
||||
pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
|
||||
RepetitionState {
|
||||
|
||||
set,
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-64
@@ -1,7 +1,9 @@
|
||||
use crate::dictionary::DictionaryElement;
|
||||
use crate::Page::PreviousPage;
|
||||
use crate::data_provider::card_sets::{delete_set, update_card_set};
|
||||
use crate::lang::DictionaryElement;
|
||||
use crate::repetition::RepetitionState;
|
||||
use crate::Page::{PreviousPage, Repetition};
|
||||
use crate::{AppState, NavigatedPage, Page, RootMessage};
|
||||
use iced::widget::button::{Catalog, Style};
|
||||
pub use iced::widget::button::{Catalog, Style};
|
||||
use iced::widget::{button, column, container, row, scrollable, space, text, text_input, Column};
|
||||
use iced::Background::Color;
|
||||
use iced::{Border, Center, Element, Fill, Left, Length, Shadow, Task, Theme};
|
||||
@@ -10,7 +12,6 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RepetitionsState {
|
||||
sets: Vec<CardSet>,
|
||||
selected_set: Option<usize>,
|
||||
correct_filters: Vec<bool>,
|
||||
pub state: Arc<Mutex<AppState>>,
|
||||
@@ -20,6 +21,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
fn navigate(&self, message: &RepetitionsMessage) -> Option<Page> {
|
||||
if let RepetitionsMessage::Back = message {
|
||||
Some(PreviousPage)
|
||||
}
|
||||
else if let RepetitionsMessage::GoToRepetition = message {
|
||||
let clone = self.state.clone();
|
||||
Some(Repetition(RepetitionState::new(self.state.lock().unwrap().card_sets[self.selected_set.unwrap()].clone(), clone) ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -28,10 +33,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
|
||||
|
||||
impl RepetitionsState {
|
||||
pub(crate) fn new(state: Arc<Mutex<AppState>>) -> RepetitionsState {
|
||||
let count = state.lock().unwrap().card_sets.len();
|
||||
RepetitionsState {
|
||||
sets: Vec::new(),
|
||||
selected_set: None,
|
||||
correct_filters: vec![],
|
||||
correct_filters: vec![true; count],
|
||||
state,
|
||||
}
|
||||
}
|
||||
@@ -39,52 +44,54 @@ impl RepetitionsState {
|
||||
|
||||
impl RepetitionsState {
|
||||
pub fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
match message {
|
||||
RepetitionsMessage::Next => {}
|
||||
RepetitionsMessage::Back => {}
|
||||
RepetitionsMessage::GoToRepetition => {}
|
||||
RepetitionsMessage::CreateSet => {
|
||||
self.sets.push(CardSet::with_name(format!(
|
||||
"Card set #{}",
|
||||
self.sets.len() + 1
|
||||
)));
|
||||
let index = state.card_sets.len() + 1;
|
||||
state
|
||||
.card_sets
|
||||
.push(CardSetSettings::with_name(format!("Card set #{}", index)));
|
||||
self.correct_filters.push(true);
|
||||
}
|
||||
RepetitionsMessage::DeleteSet => {
|
||||
self.sets.remove(self.selected_set.unwrap());
|
||||
let set = state.card_sets.remove(self.selected_set.unwrap());
|
||||
self.correct_filters.remove(self.selected_set.unwrap());
|
||||
self.selected_set = None;
|
||||
delete_set(&set, &state.connection);
|
||||
}
|
||||
RepetitionsMessage::SelectSet(index) => {
|
||||
self.selected_set = Some(index);
|
||||
}
|
||||
RepetitionsMessage::SetName(new) => {
|
||||
self.sets[self.selected_set.unwrap()].name = new;
|
||||
state.card_sets[self.selected_set.unwrap()].name = new;
|
||||
}
|
||||
RepetitionsMessage::Save => {
|
||||
for i in 0..self.sets.len() {
|
||||
self.correct_filters[i] = self.sets[i].check_filter();
|
||||
for i in 0..state.card_sets.len() {
|
||||
self.correct_filters[i] = state.card_sets[i].check_filter();
|
||||
}
|
||||
|
||||
if self.correct_filters.iter().all(|x| *x) {
|
||||
println!("Saving");
|
||||
} else {
|
||||
println!("Some errors");
|
||||
}
|
||||
let word = &mut state.card_sets[self.selected_set.unwrap()].clone();
|
||||
|
||||
update_card_set(word, &state.connection);
|
||||
|
||||
state.card_sets[self.selected_set.unwrap()] = word.clone();
|
||||
}
|
||||
RepetitionsMessage::SetForward(new) => {
|
||||
self.sets[self.selected_set.unwrap()].forward = new;
|
||||
state.card_sets[self.selected_set.unwrap()].forward = new;
|
||||
}
|
||||
RepetitionsMessage::SetBackward(new) => {
|
||||
self.sets[self.selected_set.unwrap()].backward = new;
|
||||
state.card_sets[self.selected_set.unwrap()].backward = new;
|
||||
}
|
||||
RepetitionsMessage::SetFilter(new) => {
|
||||
self.sets[self.selected_set.unwrap()].filter = new;
|
||||
state.card_sets[self.selected_set.unwrap()].filter = new;
|
||||
}
|
||||
RepetitionsMessage::TryFilter => {
|
||||
let set = &mut self.sets[self.selected_set.unwrap()];
|
||||
let count = set.get_word_list(&self.state.lock().unwrap()).len();
|
||||
set.count = Some(count);
|
||||
let set = state.card_sets.get(self.selected_set.unwrap()).unwrap();
|
||||
let count = set.get_word_list(&state).len();
|
||||
state.card_sets[self.selected_set.unwrap()].count = Some(count);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@@ -100,9 +107,6 @@ impl RepetitionsState {
|
||||
button("Добавить")
|
||||
.width(Fill)
|
||||
.on_press(RepetitionsMessage::CreateSet),
|
||||
button("Сохранить")
|
||||
.width(Fill)
|
||||
.on_press(RepetitionsMessage::Save),
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Length::FillPortion(1)),
|
||||
@@ -115,12 +119,12 @@ impl RepetitionsState {
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
]
|
||||
.align_x(Left)
|
||||
.width(Fill),
|
||||
.align_x(Left)
|
||||
.width(Fill),
|
||||
)
|
||||
.center_x(Fill)
|
||||
.padding(10)
|
||||
.into()
|
||||
.center_x(Fill)
|
||||
.padding(10)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn launch_button(&self) -> Element<'_, RepetitionsMessage> {
|
||||
@@ -135,43 +139,48 @@ impl RepetitionsState {
|
||||
|
||||
fn selected_set_view(&self) -> Element<'_, RepetitionsMessage> {
|
||||
if let Some(index) = self.selected_set {
|
||||
let sets = &self.state.lock().unwrap().card_sets;
|
||||
|
||||
return column![
|
||||
scrollable(
|
||||
column![
|
||||
text_input("Название набора", &self.sets[index].name)
|
||||
text_input("Название набора", &sets[index].name)
|
||||
.on_input(RepetitionsMessage::SetName),
|
||||
text_input("Передняя сторона", &self.sets[index].forward)
|
||||
text_input("Передняя сторона", &sets[index].forward)
|
||||
.on_input(RepetitionsMessage::SetForward),
|
||||
text_input("Задняя сторона", &self.sets[index].backward)
|
||||
text_input("Задняя сторона", &sets[index].backward)
|
||||
.on_input(RepetitionsMessage::SetBackward),
|
||||
text!("Фильтр"),
|
||||
text_input("", &self.sets[index].filter)
|
||||
.on_input(RepetitionsMessage::SetFilter),
|
||||
text_input("", &sets[index].filter).on_input(RepetitionsMessage::SetFilter),
|
||||
button("Проверить фильтр").on_press(RepetitionsMessage::TryFilter),
|
||||
self.count_view()
|
||||
self.count_view(&sets[index])
|
||||
]
|
||||
.spacing(10)
|
||||
)
|
||||
.height(Fill),
|
||||
button("Удалить")
|
||||
.style(|x: &Theme, _status| Style {
|
||||
background: Some(Color(x.palette().danger)),
|
||||
text_color: x.palette().text,
|
||||
border: Default::default(),
|
||||
shadow: Default::default(),
|
||||
snap: false,
|
||||
})
|
||||
.on_press(RepetitionsMessage::DeleteSet),
|
||||
row![
|
||||
button("Сохранить").on_press(RepetitionsMessage::Save),
|
||||
button("Удалить")
|
||||
.style(|x: &Theme, _status| Style {
|
||||
background: Some(Color(x.palette().danger)),
|
||||
text_color: x.palette().text,
|
||||
border: Border::default().rounded(2),
|
||||
shadow: Default::default(),
|
||||
snap: false,
|
||||
})
|
||||
.on_press(RepetitionsMessage::DeleteSet)
|
||||
]
|
||||
.spacing(10),
|
||||
]
|
||||
.spacing(10)
|
||||
.width(Length::FillPortion(2))
|
||||
.into();
|
||||
.spacing(10)
|
||||
.width(Length::FillPortion(2))
|
||||
.into();
|
||||
}
|
||||
space().width(Length::FillPortion(2)).into()
|
||||
}
|
||||
|
||||
fn count_view(&self) -> Element<'_, RepetitionsMessage> {
|
||||
if let Some(count) = self.sets[self.selected_set.unwrap()].count {
|
||||
fn count_view(&self, set: &CardSetSettings) -> Element<'_, RepetitionsMessage> {
|
||||
if let Some(count) = set.count {
|
||||
return text!("Колличество слов: {}", count).into();
|
||||
}
|
||||
space().into()
|
||||
@@ -180,7 +189,8 @@ impl RepetitionsState {
|
||||
fn sets_list(&self) -> Column<'_, RepetitionsMessage> {
|
||||
let mut column = Column::new();
|
||||
let mut i = 0;
|
||||
for set in &self.sets {
|
||||
let sets = &self.state.lock().unwrap().card_sets;
|
||||
for set in sets {
|
||||
column = column.push(
|
||||
button(text!("{}", set.name.clone()))
|
||||
.on_press_with(move || RepetitionsMessage::SelectSet(i.clone()))
|
||||
@@ -219,17 +229,19 @@ pub enum RepetitionsMessage {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CardSet {
|
||||
name: String,
|
||||
forward: String,
|
||||
backward: String,
|
||||
filter: String,
|
||||
count: Option<usize>,
|
||||
pub struct CardSetSettings {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub forward: String,
|
||||
pub backward: String,
|
||||
pub filter: String,
|
||||
pub count: Option<usize>,
|
||||
}
|
||||
|
||||
impl CardSet {
|
||||
fn with_name(name: String) -> CardSet {
|
||||
CardSet {
|
||||
impl CardSetSettings {
|
||||
fn with_name(name: String) -> CardSetSettings {
|
||||
CardSetSettings {
|
||||
id: 0,
|
||||
name,
|
||||
forward: "".to_string(),
|
||||
backward: "".to_string(),
|
||||
@@ -260,7 +272,8 @@ impl CardSet {
|
||||
more.insert(iced.0.clone().into(), iced.1.clone().into());
|
||||
}
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("key", word.key.clone())
|
||||
scope
|
||||
.push_constant("key", word.key.clone())
|
||||
.push_constant("value", word.value.clone())
|
||||
.push_constant("tags", word.tags.clone())
|
||||
.push_constant("more", more);
|
||||
|
||||
Reference in New Issue
Block a user