Add navigation to repetition page

This commit is contained in:
2026-04-13 22:25:58 +03:00
parent 8df6eae781
commit a1217f90ec
12 changed files with 674 additions and 121 deletions
Generated
+114
View File
@@ -441,6 +441,19 @@ dependencies = [
"rand_core", "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]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.1" version = "5.4.1"
@@ -826,6 +839,18 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@@ -1201,6 +1226,15 @@ dependencies = [
"foldhash 0.2.0", "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]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -1225,6 +1259,30 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" 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]] [[package]]
name = "iced" name = "iced"
version = "0.14.0" version = "0.14.0"
@@ -1437,10 +1495,12 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
name = "jap_learn" name = "jap_learn"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"chrono",
"dirs", "dirs",
"iced", "iced",
"rand", "rand",
"rhai", "rhai",
"rusqlite",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -1553,6 +1613,16 @@ dependencies = [
"redox_syscall 0.7.0", "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]] [[package]]
name = "lilt" name = "lilt"
version = "0.8.1" version = "0.8.1"
@@ -2497,6 +2567,32 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" 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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"
@@ -2811,6 +2907,18 @@ dependencies = [
"bitflags 2.10.0", "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]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@@ -3125,6 +3233,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
+2
View File
@@ -10,3 +10,5 @@ dirs = "6.0.0"
serde = { version = "1.0.144", features = ["derive"] } serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
rhai = "1.24.0" rhai = "1.24.0"
chrono = "0.4.44"
rusqlite = { version = "0.39.0", features = ["chrono"] }
+76
View File
@@ -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
});
}
+76
View File
@@ -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
});
}
+7
View File
@@ -0,0 +1,7 @@
pub(crate) mod words;
pub(crate) mod card_sets;
pub(crate) mod card_stats;
use crate::lang;
use crate::dictionary;
+150
View File
@@ -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
View File
@@ -1,11 +1,12 @@
use crate::data_provider::words::{delete_word, update_word};
use crate::dictionary::DictionaryMessage::Test; use crate::dictionary::DictionaryMessage::Test;
use crate::dictionary_test::DictionaryQuizState; use crate::dictionary_test::DictionaryQuizState;
use crate::lang::DictionaryElement;
use crate::{AppState, NavigatedPage, Page, RootMessage}; use crate::{AppState, NavigatedPage, Page, RootMessage};
use iced::alignment::Vertical::Center; use iced::alignment::Vertical::Center;
use iced::widget::button::Style; use iced::widget::button::Style;
use iced::widget::*; use iced::widget::*;
use iced::{Border, Color, Length, Shadow, Task}; use iced::{Border, Color, Length, Shadow, Task};
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@@ -22,31 +23,13 @@ pub struct DictionaryState {
no_typing: bool, 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)] #[derive(Debug, Clone)]
pub enum DictionaryMessage { pub enum DictionaryMessage {
Back, Back,
SetTags(usize, String), SetTags(usize, String),
SetKey(usize, String), SetKey(usize, String),
SetValue(usize, String), SetValue(usize, String),
SubmitWord(usize),
Remove(usize), Remove(usize),
NewWord, NewWord,
Include(usize, bool), Include(usize, bool),
@@ -109,6 +92,7 @@ impl DictionaryState {
dict.push(DictionaryElement::new()); dict.push(DictionaryElement::new());
self.include_map.push(false); self.include_map.push(false);
} }
DictionaryMessage::SetKey(i, v) => { DictionaryMessage::SetKey(i, v) => {
let dict = &mut self.state.lock().unwrap().dictionary; let dict = &mut self.state.lock().unwrap().dictionary;
dict[i].key = v dict[i].key = v
@@ -116,7 +100,7 @@ impl DictionaryState {
DictionaryMessage::SetValue(i, v) => { DictionaryMessage::SetValue(i, v) => {
let dict = &mut self.state.lock().unwrap().dictionary; let dict = &mut self.state.lock().unwrap().dictionary;
dict[i].value = v dict[i].value = v
}, }
DictionaryMessage::SetTags(i, v) => { DictionaryMessage::SetTags(i, v) => {
{ {
let dict = &mut self.state.lock().unwrap().dictionary; let dict = &mut self.state.lock().unwrap().dictionary;
@@ -125,32 +109,45 @@ impl DictionaryState {
self.update_tags(); self.update_tags();
} }
DictionaryMessage::Remove(i) => { DictionaryMessage::Remove(i) => {
let dict = &mut self.state.lock().unwrap().dictionary; let state = &mut self.state.lock().unwrap();
dict.remove(i); 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::Include(i, b) => self.include_map[i] = b,
DictionaryMessage::IncludeTag(t, v) => { DictionaryMessage::IncludeTag(t, v) => {
self.tag_map.insert(t, v); self.tag_map.insert(t, v);
self.update_words_include() self.update_words_include()
} }
DictionaryMessage::ResetTags => { DictionaryMessage::ResetTags => {
self.tag_map.iter_mut().for_each(|(_, v)| *v = false); self.tag_map.iter_mut().for_each(|(_, v)| *v = false);
self.include_map.iter_mut().for_each(|x| *x = false) self.include_map.iter_mut().for_each(|x| *x = false)
} }
DictionaryMessage::Save => { DictionaryMessage::Save => {
let dir = dict_file(); /* let dir = dict_file();
let dict = &self.state.lock().unwrap().dictionary; let dict = &self.state.lock().unwrap().dictionary;
let content = serde_json::to_string_pretty(&dict.clone()).unwrap(); let content = serde_json::to_string_pretty(&dict.clone()).unwrap();
fs::write(dir, content.clone()) 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::SetReverse(v) => self.reverse = v,
DictionaryMessage::Search(s) => { DictionaryMessage::Search(s) => {
self.search = s; self.search = s;
} }
DictionaryMessage::SetTyping(b) => self.no_typing = b, 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) text_input("Ключ", &word.key)
.size(20) .size(20)
.width(Length::Fill) .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( line = line.push(
text_input("Значение", &word.value) text_input("Значение", &word.value)
.size(20) .size(20)
.width(Length::Fill) .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( line = line.push(
text_input("Тэги", &word.tags) text_input("Тэги", &word.tags)
.size(20) .size(20)
.width(Length::Fill) .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 line = line
@@ -364,3 +364,13 @@ pub fn dict_file() -> PathBuf {
dir.push("dict.json"); dir.push("dict.json");
dir 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
}
+3 -1
View File
@@ -1,4 +1,4 @@
use crate::dictionary::{split_with_coma, DictionaryElement}; use crate::dictionary::{split_with_coma};
use crate::quiz::Score; use crate::quiz::Score;
use crate::Page::PreviousPage; use crate::Page::PreviousPage;
use crate::RootMessage; use crate::RootMessage;
@@ -9,6 +9,8 @@ use iced::widget::{button, container, row, space, text, text_input, Row};
use iced::Background::Color; use iced::Background::Color;
use iced::{alignment, Border, Element, Fill, Task, Theme}; use iced::{alignment, Border, Element, Fill, Task, Theme};
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use crate::lang::DictionaryElement;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DictionaryQuizState { pub struct DictionaryQuizState {
words: Vec<DictionaryElement>, words: Vec<DictionaryElement>,
+102 -1
View File
@@ -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)] #[derive(Clone, Debug)]
pub struct KanaSet { pub struct KanaSet {
name: String, 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 current_set = self.list();
let mut rand = rand::rng(); let mut rand = rand::rng();
@@ -210,3 +220,94 @@ impl PartialEq<Self> for KanaSet {
self.name == other.name 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,
}
}
}
+18 -16
View File
@@ -8,15 +8,16 @@ mod repetition;
mod repetitions; mod repetitions;
mod selector; mod selector;
mod writing; mod writing;
mod data_provider;
use std::fs::File; use crate::data_provider::card_sets::load_sets;
use std::io::Read; use crate::data_provider::words::{create_db, load_words};
use crate::dictionary::{DictionaryElement, DictionaryMessage, DictionaryState}; use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState}; use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
use crate::lang::DictionaryElement;
use crate::quiz::*; use crate::quiz::*;
use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState}; use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState};
use crate::repetition::{RepetitionMessage, RepetitionState}; use crate::repetition::{RepetitionMessage, RepetitionState};
use crate::repetitions::{RepetitionsMessage, RepetitionsState}; use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState};
use crate::selector::*; use crate::selector::*;
use crate::writing::{WritingMessage, WritingState}; use crate::writing::{WritingMessage, WritingState};
use crate::Page::{ use crate::Page::{
@@ -25,6 +26,7 @@ use crate::Page::{
use iced::widget::text; use iced::widget::text;
use iced::Element; use iced::Element;
use iced::{Font, Task}; use iced::{Font, Task};
use rusqlite::Connection;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
fn main() -> iced::Result { fn main() -> iced::Result {
@@ -32,7 +34,8 @@ fn main() -> iced::Result {
.title("Kana learn app") .title("Kana learn app")
.font(include_bytes!("../noto.ttf")) .font(include_bytes!("../noto.ttf"))
.default_font(Font::with_name("Noto Sans JP")) .default_font(Font::with_name("Noto Sans JP"))
.run() .
run()
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -65,21 +68,19 @@ pub struct ScreenState {
pub struct AppState { pub struct AppState {
pub dictionary: Vec<DictionaryElement>, pub dictionary: Vec<DictionaryElement>,
pub card_sets: Vec<CardSetSettings>,
pub connection: Connection
} }
impl Default for ScreenState { impl Default for ScreenState {
fn default() -> Self { fn default() -> Self {
let mut current_dict = "[]".to_string(); let path = app_data_dir();
match File::open(dictionary::dict_file()) { let db_file = path.join("data.db");
Ok(mut f) => { let connection = Connection::open(db_file).unwrap();
current_dict = String::new(); let list: Vec<DictionaryElement> = load_words(&connection);
f.read_to_string(&mut current_dict).unwrap(); let sets: Vec<CardSetSettings> = load_sets(&connection);
}
_ => {}
}
let list: Vec<DictionaryElement> = serde_json::from_str(&current_dict).unwrap(); let state = Arc::new(Mutex::new(AppState { dictionary: list, card_sets: sets, connection }));
let state = Arc::new(Mutex::new(AppState { dictionary: list }));
ScreenState { ScreenState {
stack: vec![Selector(SelectorState::new(state.clone()))], stack: vec![Selector(SelectorState::new(state.clone()))],
} }
@@ -88,6 +89,7 @@ impl Default for ScreenState {
impl ScreenState { impl ScreenState {
pub fn boot() -> (ScreenState, Task<RootMessage>) { pub fn boot() -> (ScreenState, Task<RootMessage>) {
create_db();
(ScreenState::default(), Task::none()) (ScreenState::default(), Task::none())
} }
pub fn update(&mut self, message: RootMessage) -> Task<RootMessage> { pub fn update(&mut self, message: RootMessage) -> Task<RootMessage> {
+10 -10
View File
@@ -1,16 +1,15 @@
use std::sync::{Arc, Mutex};
use crate::Page::PreviousPage; use crate::Page::PreviousPage;
use crate::{NavigatedPage, Page, RootMessage}; use crate::{AppState, NavigatedPage, Page, RootMessage};
use iced::widget::{button, container}; use iced::widget::{button, container};
use iced::{Element, Fill, Left, Task}; use iced::{Element, Fill, Left, Task};
use crate::lang::CardSet;
use crate::repetitions::CardSetSettings;
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct RepetitionState { pub struct RepetitionState {
} pub set: CardSetSettings,
pub state: Arc<Mutex<AppState>>,
impl Default for RepetitionState {
fn default() -> Self {
RepetitionState::new()
}
} }
impl NavigatedPage<RepetitionMessage> for RepetitionState { impl NavigatedPage<RepetitionMessage> for RepetitionState {
@@ -24,9 +23,10 @@ impl NavigatedPage<RepetitionMessage> for RepetitionState {
} }
impl RepetitionState { impl RepetitionState {
pub(crate) fn new() -> RepetitionState { pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
RepetitionState { RepetitionState {
set,
state
} }
} }
} }
+62 -49
View File
@@ -1,7 +1,9 @@
use crate::dictionary::DictionaryElement; use crate::data_provider::card_sets::{delete_set, update_card_set};
use crate::Page::PreviousPage; use crate::lang::DictionaryElement;
use crate::repetition::RepetitionState;
use crate::Page::{PreviousPage, Repetition};
use crate::{AppState, NavigatedPage, Page, RootMessage}; 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::widget::{button, column, container, row, scrollable, space, text, text_input, Column};
use iced::Background::Color; use iced::Background::Color;
use iced::{Border, Center, Element, Fill, Left, Length, Shadow, Task, Theme}; use iced::{Border, Center, Element, Fill, Left, Length, Shadow, Task, Theme};
@@ -10,7 +12,6 @@ use std::sync::{Arc, Mutex};
#[derive(Clone)] #[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
sets: Vec<CardSet>,
selected_set: Option<usize>, selected_set: Option<usize>,
correct_filters: Vec<bool>, correct_filters: Vec<bool>,
pub state: Arc<Mutex<AppState>>, pub state: Arc<Mutex<AppState>>,
@@ -20,6 +21,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
fn navigate(&self, message: &RepetitionsMessage) -> Option<Page> { fn navigate(&self, message: &RepetitionsMessage) -> Option<Page> {
if let RepetitionsMessage::Back = message { if let RepetitionsMessage::Back = message {
Some(PreviousPage) 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 { } else {
None None
} }
@@ -28,10 +33,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
impl RepetitionsState { impl RepetitionsState {
pub(crate) fn new(state: Arc<Mutex<AppState>>) -> RepetitionsState { pub(crate) fn new(state: Arc<Mutex<AppState>>) -> RepetitionsState {
let count = state.lock().unwrap().card_sets.len();
RepetitionsState { RepetitionsState {
sets: Vec::new(),
selected_set: None, selected_set: None,
correct_filters: vec![], correct_filters: vec![true; count],
state, state,
} }
} }
@@ -39,52 +44,54 @@ impl RepetitionsState {
impl RepetitionsState { impl RepetitionsState {
pub fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> { pub fn update(&mut self, message: RepetitionsMessage) -> Task<RootMessage> {
let mut state = self.state.lock().unwrap();
match message { match message {
RepetitionsMessage::Next => {} RepetitionsMessage::Next => {}
RepetitionsMessage::Back => {} RepetitionsMessage::Back => {}
RepetitionsMessage::GoToRepetition => {} RepetitionsMessage::GoToRepetition => {}
RepetitionsMessage::CreateSet => { RepetitionsMessage::CreateSet => {
self.sets.push(CardSet::with_name(format!( let index = state.card_sets.len() + 1;
"Card set #{}", state
self.sets.len() + 1 .card_sets
))); .push(CardSetSettings::with_name(format!("Card set #{}", index)));
self.correct_filters.push(true); self.correct_filters.push(true);
} }
RepetitionsMessage::DeleteSet => { 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.correct_filters.remove(self.selected_set.unwrap());
self.selected_set = None; self.selected_set = None;
delete_set(&set, &state.connection);
} }
RepetitionsMessage::SelectSet(index) => { RepetitionsMessage::SelectSet(index) => {
self.selected_set = Some(index); self.selected_set = Some(index);
} }
RepetitionsMessage::SetName(new) => { RepetitionsMessage::SetName(new) => {
self.sets[self.selected_set.unwrap()].name = new; state.card_sets[self.selected_set.unwrap()].name = new;
} }
RepetitionsMessage::Save => { RepetitionsMessage::Save => {
for i in 0..self.sets.len() { for i in 0..state.card_sets.len() {
self.correct_filters[i] = self.sets[i].check_filter(); self.correct_filters[i] = state.card_sets[i].check_filter();
} }
if self.correct_filters.iter().all(|x| *x) { let word = &mut state.card_sets[self.selected_set.unwrap()].clone();
println!("Saving");
} else { update_card_set(word, &state.connection);
println!("Some errors");
} state.card_sets[self.selected_set.unwrap()] = word.clone();
} }
RepetitionsMessage::SetForward(new) => { RepetitionsMessage::SetForward(new) => {
self.sets[self.selected_set.unwrap()].forward = new; state.card_sets[self.selected_set.unwrap()].forward = new;
} }
RepetitionsMessage::SetBackward(new) => { RepetitionsMessage::SetBackward(new) => {
self.sets[self.selected_set.unwrap()].backward = new; state.card_sets[self.selected_set.unwrap()].backward = new;
} }
RepetitionsMessage::SetFilter(new) => { RepetitionsMessage::SetFilter(new) => {
self.sets[self.selected_set.unwrap()].filter = new; state.card_sets[self.selected_set.unwrap()].filter = new;
} }
RepetitionsMessage::TryFilter => { RepetitionsMessage::TryFilter => {
let set = &mut self.sets[self.selected_set.unwrap()]; let set = state.card_sets.get(self.selected_set.unwrap()).unwrap();
let count = set.get_word_list(&self.state.lock().unwrap()).len(); let count = set.get_word_list(&state).len();
set.count = Some(count); state.card_sets[self.selected_set.unwrap()].count = Some(count);
} }
} }
Task::none() Task::none()
@@ -100,9 +107,6 @@ impl RepetitionsState {
button("Добавить") button("Добавить")
.width(Fill) .width(Fill)
.on_press(RepetitionsMessage::CreateSet), .on_press(RepetitionsMessage::CreateSet),
button("Сохранить")
.width(Fill)
.on_press(RepetitionsMessage::Save),
] ]
.spacing(10) .spacing(10)
.width(Length::FillPortion(1)), .width(Length::FillPortion(1)),
@@ -135,33 +139,38 @@ impl RepetitionsState {
fn selected_set_view(&self) -> Element<'_, RepetitionsMessage> { fn selected_set_view(&self) -> Element<'_, RepetitionsMessage> {
if let Some(index) = self.selected_set { if let Some(index) = self.selected_set {
let sets = &self.state.lock().unwrap().card_sets;
return column![ return column![
scrollable( scrollable(
column![ column![
text_input("Название набора", &self.sets[index].name) text_input("Название набора", &sets[index].name)
.on_input(RepetitionsMessage::SetName), .on_input(RepetitionsMessage::SetName),
text_input("Передняя сторона", &self.sets[index].forward) text_input("Передняя сторона", &sets[index].forward)
.on_input(RepetitionsMessage::SetForward), .on_input(RepetitionsMessage::SetForward),
text_input("Задняя сторона", &self.sets[index].backward) text_input("Задняя сторона", &sets[index].backward)
.on_input(RepetitionsMessage::SetBackward), .on_input(RepetitionsMessage::SetBackward),
text!("Фильтр"), text!("Фильтр"),
text_input("", &self.sets[index].filter) text_input("", &sets[index].filter).on_input(RepetitionsMessage::SetFilter),
.on_input(RepetitionsMessage::SetFilter),
button("Проверить фильтр").on_press(RepetitionsMessage::TryFilter), button("Проверить фильтр").on_press(RepetitionsMessage::TryFilter),
self.count_view() self.count_view(&sets[index])
] ]
.spacing(10) .spacing(10)
) )
.height(Fill), .height(Fill),
row![
button("Сохранить").on_press(RepetitionsMessage::Save),
button("Удалить") button("Удалить")
.style(|x: &Theme, _status| Style { .style(|x: &Theme, _status| Style {
background: Some(Color(x.palette().danger)), background: Some(Color(x.palette().danger)),
text_color: x.palette().text, text_color: x.palette().text,
border: Default::default(), border: Border::default().rounded(2),
shadow: Default::default(), shadow: Default::default(),
snap: false, snap: false,
}) })
.on_press(RepetitionsMessage::DeleteSet), .on_press(RepetitionsMessage::DeleteSet)
]
.spacing(10),
] ]
.spacing(10) .spacing(10)
.width(Length::FillPortion(2)) .width(Length::FillPortion(2))
@@ -170,8 +179,8 @@ impl RepetitionsState {
space().width(Length::FillPortion(2)).into() space().width(Length::FillPortion(2)).into()
} }
fn count_view(&self) -> Element<'_, RepetitionsMessage> { fn count_view(&self, set: &CardSetSettings) -> Element<'_, RepetitionsMessage> {
if let Some(count) = self.sets[self.selected_set.unwrap()].count { if let Some(count) = set.count {
return text!("Колличество слов: {}", count).into(); return text!("Колличество слов: {}", count).into();
} }
space().into() space().into()
@@ -180,7 +189,8 @@ impl RepetitionsState {
fn sets_list(&self) -> Column<'_, RepetitionsMessage> { fn sets_list(&self) -> Column<'_, RepetitionsMessage> {
let mut column = Column::new(); let mut column = Column::new();
let mut i = 0; let mut i = 0;
for set in &self.sets { let sets = &self.state.lock().unwrap().card_sets;
for set in sets {
column = column.push( column = column.push(
button(text!("{}", set.name.clone())) button(text!("{}", set.name.clone()))
.on_press_with(move || RepetitionsMessage::SelectSet(i.clone())) .on_press_with(move || RepetitionsMessage::SelectSet(i.clone()))
@@ -219,17 +229,19 @@ pub enum RepetitionsMessage {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CardSet { pub struct CardSetSettings {
name: String, pub id: u32,
forward: String, pub name: String,
backward: String, pub forward: String,
filter: String, pub backward: String,
count: Option<usize>, pub filter: String,
pub count: Option<usize>,
} }
impl CardSet { impl CardSetSettings {
fn with_name(name: String) -> CardSet { fn with_name(name: String) -> CardSetSettings {
CardSet { CardSetSettings {
id: 0,
name, name,
forward: "".to_string(), forward: "".to_string(),
backward: "".to_string(), backward: "".to_string(),
@@ -260,7 +272,8 @@ impl CardSet {
more.insert(iced.0.clone().into(), iced.1.clone().into()); more.insert(iced.0.clone().into(), iced.1.clone().into());
} }
let mut scope = Scope::new(); 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("value", word.value.clone())
.push_constant("tags", word.tags.clone()) .push_constant("tags", word.tags.clone())
.push_constant("more", more); .push_constant("more", more);