Add word group to database and model
This commit is contained in:
+76
-59
@@ -1,5 +1,5 @@
|
|||||||
use crate::dictionary::app_data_dir;
|
use crate::dictionary::app_data_dir;
|
||||||
use crate::lang::DictionaryElement;
|
use crate::lang::{WordData, WordGroup};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -10,13 +10,61 @@ pub fn create_db() {
|
|||||||
std::fs::File::create(&db_file).unwrap();
|
std::fs::File::create(&db_file).unwrap();
|
||||||
let connection = Connection::open(&db_file).unwrap();
|
let connection = Connection::open(&db_file).unwrap();
|
||||||
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
|
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
|
||||||
create_words_table(&connection);
|
|
||||||
create_card_stat_table(&connection);
|
create_tables(&connection);
|
||||||
create_card_set_table(&connection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_word(word: &mut DictionaryElement, connection: &Connection) {
|
fn create_tables(conn: &Connection) {
|
||||||
|
conn.execute(
|
||||||
|
"
|
||||||
|
CREATE TABLE word_group (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into word_group (name)
|
||||||
|
values (\"Слова\");
|
||||||
|
|
||||||
|
CREATE TABLE words (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
tags TEXT NOT NULL,
|
||||||
|
more TEXT,
|
||||||
|
group_id INTEGER NOT NULL DEFAULT 1,
|
||||||
|
FOREIGN KEY(group_id) REFERENCES word_group(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
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 card_set (id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 fn add_word(word: &mut WordData, connection: &Connection) {
|
||||||
let index = connection
|
let index = connection
|
||||||
.query_row(
|
.query_row(
|
||||||
"INSERT INTO words (key, value, tags, more) VALUES (?1, ?2, ?3, ?4) RETURNING id",
|
"INSERT INTO words (key, value, tags, more) VALUES (?1, ?2, ?3, ?4) RETURNING id",
|
||||||
@@ -36,7 +84,7 @@ pub fn add_word(word: &mut DictionaryElement, connection: &Connection) {
|
|||||||
word.id = index;
|
word.id = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_word(word: &mut DictionaryElement, connection: &Connection) {
|
pub fn update_word(word: &mut WordData, connection: &Connection) {
|
||||||
if word.id == 0 {
|
if word.id == 0 {
|
||||||
add_word(word, &connection);
|
add_word(word, &connection);
|
||||||
} else {
|
} else {
|
||||||
@@ -58,7 +106,7 @@ pub fn update_word(word: &mut DictionaryElement, connection: &Connection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_word(word: &DictionaryElement, connection: &Connection) {
|
pub fn delete_word(word: &WordData, connection: &Connection) {
|
||||||
if word.id == 0 {
|
if word.id == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -70,19 +118,20 @@ pub fn delete_word(word: &DictionaryElement, connection: &Connection) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_words(connection: &Connection) -> Vec<DictionaryElement> {
|
pub fn load_words(connection: &Connection) -> Vec<WordData> {
|
||||||
let mut stmt = connection
|
let mut stmt = connection
|
||||||
.prepare("SELECT id, key, value, tags, more FROM words")
|
.prepare("SELECT id, key, value, tags, more, group_id FROM words")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let word_iter = stmt
|
let word_iter = stmt
|
||||||
.query_map([], |row| {
|
.query_map([], |row| {
|
||||||
let addinionals: String = row.get(4)?;
|
let addinionals: String = row.get(4)?;
|
||||||
Ok(DictionaryElement {
|
Ok(WordData {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
key: row.get(1)?,
|
key: row.get(1)?,
|
||||||
value: row.get(2)?,
|
value: row.get(2)?,
|
||||||
tags: row.get(3)?,
|
tags: row.get(3)?,
|
||||||
additional: serde_json::from_str::<HashMap<String, String>>(&addinionals).unwrap(),
|
additional: serde_json::from_str::<HashMap<String, String>>(&addinionals).unwrap(),
|
||||||
|
group_id: row.get(5)?,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -95,55 +144,23 @@ pub fn load_words(connection: &Connection) -> Vec<DictionaryElement> {
|
|||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_words_table(conn: &Connection) {
|
pub fn load_word_groups(connection: &Connection) -> Vec<WordGroup> {
|
||||||
conn.execute(
|
let mut stmt = connection
|
||||||
"CREATE TABLE words (
|
.prepare("SELECT id, name FROM word_group")
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
.unwrap();
|
||||||
key TEXT NOT NULL,
|
let group_iter = stmt
|
||||||
value TEXT NOT NULL,
|
.query_map([], |row| {
|
||||||
tags TEXT NOT NULL,
|
Ok(WordGroup {
|
||||||
more TEXT
|
id: row.get(0)?,
|
||||||
)",
|
name: row.get(1)?,
|
||||||
(),
|
})
|
||||||
)
|
})
|
||||||
.unwrap_or_else(|e| {
|
.unwrap();
|
||||||
println!("{}", e);
|
|
||||||
0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_card_stat_table(conn: &Connection) {
|
let mut buffer = vec![];
|
||||||
conn.execute(
|
for group in group_iter {
|
||||||
"CREATE TABLE card_stats (
|
buffer.push(group.unwrap());
|
||||||
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 card_set (id) ON DELETE CASCADE
|
|
||||||
)",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
println!("{}", e);
|
|
||||||
0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_card_set_table(conn: &Connection) {
|
buffer
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
+4
-3
@@ -1,7 +1,7 @@
|
|||||||
use crate::data_provider::words::{delete_word, update_word};
|
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::lang::WordData;
|
||||||
use crate::word::WordState;
|
use crate::word::WordState;
|
||||||
use crate::Page::Word;
|
use crate::Page::Word;
|
||||||
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
||||||
@@ -41,6 +41,7 @@ pub enum DictionaryMessage {
|
|||||||
SetReverse(bool),
|
SetReverse(bool),
|
||||||
Search(String),
|
Search(String),
|
||||||
SetTyping(bool),
|
SetTyping(bool),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigatedPage<DictionaryMessage> for DictionaryState {
|
impl NavigatedPage<DictionaryMessage> for DictionaryState {
|
||||||
@@ -66,7 +67,7 @@ impl NavigatedPage<DictionaryMessage> for DictionaryState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let DictionaryMessage::WordAction(index) = message {
|
if let DictionaryMessage::WordAction(index) = message {
|
||||||
let word : DictionaryElement;
|
let word : WordData;
|
||||||
{
|
{
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
let dict = &state.dictionary;
|
let dict = &state.dictionary;
|
||||||
@@ -101,7 +102,7 @@ impl DictionaryState {
|
|||||||
match message {
|
match message {
|
||||||
DictionaryMessage::NewWord => {
|
DictionaryMessage::NewWord => {
|
||||||
let dict = &mut self.state.lock().unwrap().dictionary;
|
let dict = &mut self.state.lock().unwrap().dictionary;
|
||||||
dict.push(DictionaryElement::new());
|
dict.push(WordData::new());
|
||||||
self.include_map.push(false);
|
self.include_map.push(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ 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;
|
use crate::lang::WordData;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DictionaryQuizState {
|
pub struct DictionaryQuizState {
|
||||||
words: Vec<DictionaryElement>,
|
words: Vec<WordData>,
|
||||||
current_set: Vec<DictionaryElement>,
|
current_set: Vec<WordData>,
|
||||||
answer: String,
|
answer: String,
|
||||||
view: String,
|
view: String,
|
||||||
correct: String,
|
correct: String,
|
||||||
@@ -43,7 +43,7 @@ impl NavigatedPage<DictionaryQuizMessage> for DictionaryQuizState {
|
|||||||
|
|
||||||
impl DictionaryQuizState {
|
impl DictionaryQuizState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
words: Vec<DictionaryElement>,
|
words: Vec<WordData>,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
no_typing: bool,
|
no_typing: bool,
|
||||||
) -> DictionaryQuizState {
|
) -> DictionaryQuizState {
|
||||||
|
|||||||
+12
-5
@@ -229,15 +229,16 @@ impl PartialEq<Self> for KanaSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct DictionaryElement {
|
pub struct WordData {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub tags: String,
|
pub tags: String,
|
||||||
pub additional: HashMap<String, String>,
|
pub additional: HashMap<String, String>,
|
||||||
|
pub group_id: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DictionaryElement {
|
impl WordData {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -245,10 +246,16 @@ impl DictionaryElement {
|
|||||||
value: String::new(),
|
value: String::new(),
|
||||||
tags: String::new(),
|
tags: String::new(),
|
||||||
additional: Default::default(),
|
additional: Default::default(),
|
||||||
|
group_id: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WordGroup{
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct CardStatistics {
|
pub struct CardStatistics {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
@@ -302,7 +309,7 @@ pub enum WordOpenMode {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CardSet {
|
pub struct CardSet {
|
||||||
words: Vec<DictionaryElement>,
|
words: Vec<WordData>,
|
||||||
set: Vec<CardStatistics>,
|
set: Vec<CardStatistics>,
|
||||||
last_weights: WeightedIndex<f32>,
|
last_weights: WeightedIndex<f32>,
|
||||||
current_word_index: Option<usize>,
|
current_word_index: Option<usize>,
|
||||||
@@ -360,7 +367,7 @@ impl CardSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn next(&mut self) -> DictionaryElement {
|
pub fn next(&mut self) -> (WordData, CardStatistics) {
|
||||||
let index = self.last_weights.sample(&mut self.generator);
|
let index = self.last_weights.sample(&mut self.generator);
|
||||||
|
|
||||||
if self.history.contains(&index) {
|
if self.history.contains(&index) {
|
||||||
@@ -372,7 +379,7 @@ impl CardSet {
|
|||||||
}
|
}
|
||||||
self.history.push(index);
|
self.history.push(index);
|
||||||
self.current_word_index = Some(index);
|
self.current_word_index = Some(index);
|
||||||
self.words[index].clone()
|
(self.words[index].clone(), self.set[index].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(&mut self, status: WordOpenMode) {
|
pub fn open(&mut self, status: WordOpenMode) {
|
||||||
|
|||||||
+21
-16
@@ -1,4 +1,5 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
mod data_provider;
|
||||||
mod dictionary;
|
mod dictionary;
|
||||||
mod dictionary_test;
|
mod dictionary_test;
|
||||||
mod lang;
|
mod lang;
|
||||||
@@ -7,15 +8,14 @@ mod randomizer;
|
|||||||
mod repetition;
|
mod repetition;
|
||||||
mod repetitions;
|
mod repetitions;
|
||||||
mod selector;
|
mod selector;
|
||||||
mod writing;
|
|
||||||
mod data_provider;
|
|
||||||
mod word;
|
mod word;
|
||||||
|
mod writing;
|
||||||
|
|
||||||
use crate::data_provider::card_sets::load_sets;
|
use crate::data_provider::card_sets::load_sets;
|
||||||
use crate::data_provider::words::{create_db, load_words};
|
use crate::data_provider::words::{create_db, load_word_groups, load_words};
|
||||||
use crate::dictionary::{app_data_dir, 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::lang::{WordData, WordGroup};
|
||||||
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};
|
||||||
@@ -24,7 +24,7 @@ use crate::selector::*;
|
|||||||
use crate::word::{WordMessage, WordState};
|
use crate::word::{WordMessage, WordState};
|
||||||
use crate::writing::{WritingMessage, WritingState};
|
use crate::writing::{WritingMessage, WritingState};
|
||||||
use crate::Page::{
|
use crate::Page::{
|
||||||
Dictionary, DictionaryQuiz, Quiz, Randomizer, Repetition, Repetitions, Selector, Word, Writing
|
Dictionary, DictionaryQuiz, Quiz, Randomizer, Repetition, Repetitions, Selector, Word, Writing,
|
||||||
};
|
};
|
||||||
use crate::RootMessage::Keyboard;
|
use crate::RootMessage::Keyboard;
|
||||||
use iced::keyboard::Event;
|
use iced::keyboard::Event;
|
||||||
@@ -37,12 +37,12 @@ use std::sync::{Arc, Mutex};
|
|||||||
const DEFAULT_SPACING: f32 = 10.0;
|
const DEFAULT_SPACING: f32 = 10.0;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
iced::application(ScreenState::boot, ScreenState::update, ScreenState::view).subscription(subscription)
|
iced::application(ScreenState::boot, ScreenState::update, ScreenState::view)
|
||||||
|
.subscription(subscription)
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscription(_state: &ScreenState) -> Subscription<RootMessage> {
|
fn subscription(_state: &ScreenState) -> Subscription<RootMessage> {
|
||||||
@@ -81,9 +81,10 @@ pub struct ScreenState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub dictionary: Vec<DictionaryElement>,
|
pub dictionary: Vec<WordData>,
|
||||||
pub card_sets: Vec<CardSetSettings>,
|
pub card_sets: Vec<CardSetSettings>,
|
||||||
pub connection: Connection
|
pub word_groups: Vec<WordGroup>,
|
||||||
|
pub connection: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ScreenState {
|
impl Default for ScreenState {
|
||||||
@@ -92,10 +93,16 @@ impl Default for ScreenState {
|
|||||||
let db_file = path.join("data.db");
|
let db_file = path.join("data.db");
|
||||||
let connection = Connection::open(db_file).unwrap();
|
let connection = Connection::open(db_file).unwrap();
|
||||||
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
|
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
|
||||||
let list: Vec<DictionaryElement> = load_words(&connection);
|
let list = load_words(&connection);
|
||||||
let sets: Vec<CardSetSettings> = load_sets(&connection);
|
let sets = load_sets(&connection);
|
||||||
|
let groups = load_word_groups(&connection);
|
||||||
|
|
||||||
let state = Arc::new(Mutex::new(AppState { dictionary: list, card_sets: sets, connection }));
|
let state = Arc::new(Mutex::new(AppState {
|
||||||
|
dictionary: list,
|
||||||
|
card_sets: sets,
|
||||||
|
connection,
|
||||||
|
word_groups: groups,
|
||||||
|
}));
|
||||||
ScreenState {
|
ScreenState {
|
||||||
stack: vec![Selector(SelectorState::new(state.clone()))],
|
stack: vec![Selector(SelectorState::new(state.clone()))],
|
||||||
}
|
}
|
||||||
@@ -147,12 +154,10 @@ impl ScreenState {
|
|||||||
return match page {
|
return match page {
|
||||||
Repetition(page) => page.press(&message),
|
Repetition(page) => page.press(&message),
|
||||||
_ => Task::none(),
|
_ => Task::none(),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! view_navigation {
|
macro_rules! view_navigation {
|
||||||
($stack:expr, $($e:ident), *) => {
|
($stack:expr, $($e:ident), *) => {
|
||||||
|
|||||||
+60
-29
@@ -1,14 +1,14 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use crate::data_provider::voice::get_voice;
|
use crate::data_provider::voice::get_voice;
|
||||||
use crate::lang::{CardSet, DictionaryElement, WordOpenMode};
|
use crate::lang::{CardSet, CardStatistics, WordData, WordOpenMode};
|
||||||
use crate::repetitions::CardSetSettings;
|
use crate::repetitions::CardSetSettings;
|
||||||
use crate::Page::PreviousPage;
|
use crate::Page::PreviousPage;
|
||||||
use crate::{AppState, KeyPressedPage, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
use crate::{AppState, KeyPressedPage, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
||||||
use iced::alignment::Horizontal::Center;
|
use iced::alignment::Horizontal::Center;
|
||||||
use iced::keyboard::key::Physical::Code;
|
use iced::keyboard::key::Physical::Code;
|
||||||
use iced::widget::{button, column, container, row, rule, space, text};
|
use iced::widget::{button, column, container, row, rule, space, text, Column};
|
||||||
use iced::{alignment, keyboard, Element, Fill, Left, Task};
|
use iced::{alignment, keyboard, Element, Fill, Left, Task};
|
||||||
use rodio::MixerDeviceSink;
|
use rodio::MixerDeviceSink;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@ pub struct RepetitionState {
|
|||||||
pub settings: CardSetSettings,
|
pub settings: CardSetSettings,
|
||||||
pub set: CardSet,
|
pub set: CardSet,
|
||||||
pub state: Arc<Mutex<AppState>>,
|
pub state: Arc<Mutex<AppState>>,
|
||||||
current_word: DictionaryElement,
|
current_word: WordData,
|
||||||
|
current_statistic: CardStatistics,
|
||||||
open: bool,
|
open: bool,
|
||||||
can_play: bool,
|
can_play: bool,
|
||||||
sink: Arc<MixerDeviceSink>,
|
sink: Arc<MixerDeviceSink>,
|
||||||
@@ -36,7 +37,7 @@ impl NavigatedPage<RepetitionMessage> for RepetitionState {
|
|||||||
impl RepetitionState {
|
impl RepetitionState {
|
||||||
pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
|
pub(crate) fn new(set: CardSetSettings, state: Arc<Mutex<AppState>>) -> RepetitionState {
|
||||||
let mut card_set = CardSet::new(&set, state.clone());
|
let mut card_set = CardSet::new(&set, state.clone());
|
||||||
let word = card_set.next();
|
let (word, stat) = card_set.next();
|
||||||
let sink_handle = rodio::DeviceSinkBuilder::open_default_sink().unwrap();
|
let sink_handle = rodio::DeviceSinkBuilder::open_default_sink().unwrap();
|
||||||
|
|
||||||
RepetitionState {
|
RepetitionState {
|
||||||
@@ -44,6 +45,7 @@ impl RepetitionState {
|
|||||||
set: card_set,
|
set: card_set,
|
||||||
state,
|
state,
|
||||||
current_word: word,
|
current_word: word,
|
||||||
|
current_statistic: stat,
|
||||||
open: false,
|
open: false,
|
||||||
can_play: true,
|
can_play: true,
|
||||||
sink: Arc::new(sink_handle),
|
sink: Arc::new(sink_handle),
|
||||||
@@ -60,15 +62,17 @@ impl RepetitionState {
|
|||||||
RepetitionMessage::Answer(m) => return self.answer(m),
|
RepetitionMessage::Answer(m) => return self.answer(m),
|
||||||
RepetitionMessage::Play => {
|
RepetitionMessage::Play => {
|
||||||
if !self.can_play {
|
if !self.can_play {
|
||||||
return Task::none()
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.can_play = false;
|
self.can_play = false;
|
||||||
let value = self.current_word.key.clone();
|
let value = self.current_word.key.clone();
|
||||||
if self.settings.require_speech() {
|
if self.settings.require_speech() {
|
||||||
return Task::perform(play_sound(self.sink.clone(), value), |_| RootMessage::Repetition(RepetitionMessage::PlayFinished));
|
return Task::perform(play_sound(self.sink.clone(), value), |_| {
|
||||||
|
RootMessage::Repetition(RepetitionMessage::PlayFinished)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
RepetitionMessage::PlayFinished => {
|
RepetitionMessage::PlayFinished => {
|
||||||
self.can_play = true;
|
self.can_play = true;
|
||||||
}
|
}
|
||||||
@@ -77,8 +81,6 @@ impl RepetitionState {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn next(&mut self) -> Task<RootMessage> {
|
fn next(&mut self) -> Task<RootMessage> {
|
||||||
if self.open {
|
if self.open {
|
||||||
self.answer(WordOpenMode::None)
|
self.answer(WordOpenMode::None)
|
||||||
@@ -96,10 +98,15 @@ impl RepetitionState {
|
|||||||
self.set.open(mode);
|
self.set.open(mode);
|
||||||
self.open = false;
|
self.open = false;
|
||||||
self.opened.insert(self.current_word.id);
|
self.opened.insert(self.current_word.id);
|
||||||
self.current_word = self.set.next();
|
let next = self.set.next();
|
||||||
|
self.current_word = next.0;
|
||||||
|
self.current_statistic = next.1;
|
||||||
|
|
||||||
if self.settings.require_speech() {
|
if self.settings.require_speech() {
|
||||||
return Task::perform(play_sound(self.sink.clone(), self.current_word.key.clone()), |_| RootMessage::Repetition(RepetitionMessage::PlayFinished));
|
return Task::perform(
|
||||||
|
play_sound(self.sink.clone(), self.current_word.key.clone()),
|
||||||
|
|_| RootMessage::Repetition(RepetitionMessage::PlayFinished),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
@@ -123,8 +130,13 @@ impl RepetitionState {
|
|||||||
container(self.answer_bar())
|
container(self.answer_bar())
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.align_x(Center)
|
.align_x(Center)
|
||||||
.height(30),
|
.height(60),
|
||||||
text!("Затронуто слов {}, {}%", self.opened.len(), (self.opened.len() as f32 / self.set.len() as f32 * 10000.0).round() / 100.0)
|
text!(
|
||||||
|
"Затронуто слов {}, {}%",
|
||||||
|
self.opened.len(),
|
||||||
|
(self.opened.len() as f32 / self.set.len() as f32 * 10000.0).round()
|
||||||
|
/ 100.0
|
||||||
|
)
|
||||||
]
|
]
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
@@ -138,13 +150,8 @@ impl RepetitionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_forward(&self) -> Element<'_, RepetitionMessage> {
|
fn draw_forward(&self) -> Element<'_, RepetitionMessage> {
|
||||||
let word = &self.current_word;
|
self.draw_card_view(self.settings.forward.as_str())
|
||||||
match self.settings.forward.as_str() {
|
|
||||||
"key" => self.draw_key(word),
|
|
||||||
"value" => self.draw_value(word),
|
|
||||||
"speech" => self.draw_voice(),
|
|
||||||
_ => space().into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_backward(&self) -> Element<'_, RepetitionMessage> {
|
fn draw_backward(&self) -> Element<'_, RepetitionMessage> {
|
||||||
@@ -152,13 +159,26 @@ impl RepetitionState {
|
|||||||
return space().into();
|
return space().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.draw_card_view(self.settings.backward.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_card_view(&self, properties: &str) -> Element<'_, RepetitionMessage> {
|
||||||
let word = &self.current_word;
|
let word = &self.current_word;
|
||||||
match self.settings.backward.as_str() {
|
|
||||||
|
let mut col = Column::new();
|
||||||
|
|
||||||
|
for view_type in properties.split(" ") {
|
||||||
|
col = col.push( match view_type {
|
||||||
"key" => self.draw_key(word),
|
"key" => self.draw_key(word),
|
||||||
"value" => self.draw_value(word),
|
"value" => self.draw_value(word),
|
||||||
"speech" => self.draw_voice(),
|
"speech" => self.draw_voice(),
|
||||||
|
"reading" => self.draw_reading(word),
|
||||||
_ => space().into(),
|
_ => space().into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
col.spacing(DEFAULT_SPACING).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn answer_bar(&self) -> Element<'_, RepetitionMessage> {
|
fn answer_bar(&self) -> Element<'_, RepetitionMessage> {
|
||||||
@@ -166,6 +186,8 @@ impl RepetitionState {
|
|||||||
return space().into();
|
return space().into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
column![
|
||||||
|
text!("{} очков", self.current_statistic.calculated_score().round() as i32),
|
||||||
row![
|
row![
|
||||||
button("Не получилось").on_press(RepetitionMessage::Answer(WordOpenMode::None)),
|
button("Не получилось").on_press(RepetitionMessage::Answer(WordOpenMode::None)),
|
||||||
button("Трудно").on_press(RepetitionMessage::Answer(WordOpenMode::Hard)),
|
button("Трудно").on_press(RepetitionMessage::Answer(WordOpenMode::Hard)),
|
||||||
@@ -173,22 +195,31 @@ impl RepetitionState {
|
|||||||
button("Легко").on_press(RepetitionMessage::Answer(WordOpenMode::Easy)),
|
button("Легко").on_press(RepetitionMessage::Answer(WordOpenMode::Easy)),
|
||||||
]
|
]
|
||||||
.spacing(DEFAULT_SPACING)
|
.spacing(DEFAULT_SPACING)
|
||||||
|
]
|
||||||
|
.align_x(Center)
|
||||||
|
.spacing(DEFAULT_SPACING)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_key(&self, word: &DictionaryElement) -> Element<'_, RepetitionMessage> {
|
fn draw_key(&self, word: &WordData) -> Element<'_, RepetitionMessage> {
|
||||||
text!("{}", word.key).size(36).into()
|
text!("{}", word.key).size(36).into()
|
||||||
}
|
}
|
||||||
fn draw_value(&self, word: &DictionaryElement) -> Element<'_, RepetitionMessage> {
|
fn draw_value(&self, word: &WordData) -> Element<'_, RepetitionMessage> {
|
||||||
text!("{}", word.value).size(24).into()
|
text!("{}", word.value).size(24).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_voice(&self) -> Element<'_, RepetitionMessage> {
|
fn draw_voice(&self) -> Element<'_, RepetitionMessage> {
|
||||||
|
|
||||||
button("Воспроизвести")
|
button("Воспроизвести")
|
||||||
.on_press(RepetitionMessage::Play)
|
.on_press(RepetitionMessage::Play)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_reading(&self, word: &WordData) -> Element<'_, RepetitionMessage> {
|
||||||
|
match word.additional.get("reading") {
|
||||||
|
None => space().into(),
|
||||||
|
Some(reading) => text!("{}", reading).size(24).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyPressedPage for RepetitionState {
|
impl KeyPressedPage for RepetitionState {
|
||||||
@@ -211,12 +242,10 @@ impl KeyPressedPage for RepetitionState {
|
|||||||
keyboard::key::Code::Digit3 => self.answer(WordOpenMode::Ok),
|
keyboard::key::Code::Digit3 => self.answer(WordOpenMode::Ok),
|
||||||
keyboard::key::Code::Digit4 => self.answer(WordOpenMode::Easy),
|
keyboard::key::Code::Digit4 => self.answer(WordOpenMode::Easy),
|
||||||
_ => Task::none(),
|
_ => Task::none(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,5 +262,7 @@ async fn play_sound(sink: Arc<MixerDeviceSink>, text: String) {
|
|||||||
let data = get_voice(text.as_str()).await;
|
let data = get_voice(text.as_str()).await;
|
||||||
spawn_blocking(move || {
|
spawn_blocking(move || {
|
||||||
rodio::play(&sink.mixer(), data).unwrap().sleep_until_end();
|
rodio::play(&sink.mixer(), data).unwrap().sleep_until_end();
|
||||||
}).await.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
use crate::data_provider::card_sets::{delete_set, update_card_set};
|
use crate::data_provider::card_sets::{delete_set, update_card_set};
|
||||||
use crate::lang::DictionaryElement;
|
use crate::lang::WordData;
|
||||||
use crate::repetition::RepetitionState;
|
use crate::repetition::RepetitionState;
|
||||||
use crate::Page::{PreviousPage, Repetition};
|
use crate::Page::{PreviousPage, Repetition};
|
||||||
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
||||||
@@ -255,7 +255,7 @@ impl CardSetSettings {
|
|||||||
ast.is_ok()
|
ast.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_word_list(&self, state: &AppState) -> Vec<DictionaryElement> {
|
pub fn get_word_list(&self, state: &AppState) -> Vec<WordData> {
|
||||||
let mut list = vec![];
|
let mut list = vec![];
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
let ast = engine.compile(&self.filter);
|
let ast = engine.compile(&self.filter);
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
use crate::data_provider::words::{delete_word, update_word};
|
use crate::data_provider::words::{delete_word, update_word};
|
||||||
use crate::lang::DictionaryElement;
|
use crate::lang::WordData;
|
||||||
use crate::Page::PreviousPage;
|
use crate::Page::PreviousPage;
|
||||||
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
use crate::{AppState, NavigatedPage, Page, RootMessage, DEFAULT_SPACING};
|
||||||
use iced::widget::button::danger;
|
use iced::widget::button::danger;
|
||||||
@@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
pub struct WordState {
|
pub struct WordState {
|
||||||
state: Arc<Mutex<AppState>>,
|
state: Arc<Mutex<AppState>>,
|
||||||
index: usize,
|
index: usize,
|
||||||
word: DictionaryElement,
|
word: WordData,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigatedPage<WordMessage> for WordState {
|
impl NavigatedPage<WordMessage> for WordState {
|
||||||
@@ -26,7 +26,7 @@ impl NavigatedPage<WordMessage> for WordState {
|
|||||||
|
|
||||||
impl WordState {
|
impl WordState {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
word: DictionaryElement,
|
word: WordData,
|
||||||
index: usize,
|
index: usize,
|
||||||
state: Arc<Mutex<AppState>>,
|
state: Arc<Mutex<AppState>>,
|
||||||
) -> WordState {
|
) -> WordState {
|
||||||
|
|||||||
Reference in New Issue
Block a user