App is building

This commit is contained in:
micialware committed 2026-09-07 10:53:09 +03:00
1 parent 7e1bb4bc22
commit f9c4bf9c03
5 files changed
+108 -94

No files matched your search

+1 -1
View File
@@ -138,7 +138,7 @@ impl DeckData {
history.create_now(HistoryNote { history.create_now(HistoryNote {
id: Default::default(), id: Default::default(),
timestamp: Utc::now(), timestamp: Utc::now(),
set_id: self.settings.id, deck_id: self.settings.id,
word_id, word_id,
answer: status.clone(), answer: status.clone(),
before: old_score, before: old_score,
+28 -2
View File
@@ -1,6 +1,6 @@
use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, WordOpenMode}; use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, WordOpenMode};
use crate::data::{INVALID_ID, Id}; use crate::data::{INVALID_ID, Id};
use chrono::{DateTime, NaiveDate, Utc}; use chrono::{DateTime, Local, NaiveDate, Utc};
use fsrs::MemoryState; use fsrs::MemoryState;
use litemap::LiteMap; use litemap::LiteMap;
use log::info; use log::info;
@@ -8,7 +8,9 @@ use rhai::Engine;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use crate::data_provider::history::{HistoryRepository, HistoryRepositoryInner};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct WordData { pub struct WordData {
@@ -63,7 +65,7 @@ impl WordData {
pub struct HistoryNote { pub struct HistoryNote {
pub id: Id, pub id: Id,
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
pub set_id: Id, pub deck_id: Id,
pub word_id: Id, pub word_id: Id,
pub answer: WordOpenMode, pub answer: WordOpenMode,
pub before: u8, pub before: u8,
@@ -81,6 +83,30 @@ impl EntityData for HistoryNote {
} }
} }
impl HistoryRepositoryInner {
pub fn activity_of_deck(&self, id: Id) -> Vec<(NaiveDate, u32)> {
let mut query = self.query(Arc::new(move |note| note.deck_id == id));
let values = query.iter(&self).collect::<Vec<_>>();
let mut counts = vec![];
if !values.is_empty() {
let mut date = values[0].timestamp.with_timezone(&Local).date_naive();
let mut count = 0;
for note in values {
let current_date = note.timestamp.with_timezone(&Local).date_naive();
if date == current_date{
count += 1;
}
else {
counts.push((date, count));
date = current_date;
count = 0;
}
}
}
counts
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct DeckSettings { pub struct DeckSettings {
pub id: Id, pub id: Id,
+3 -3
View File
@@ -32,7 +32,7 @@ impl TableAccessQuerySet<HistoryNote> for HistoryQuerySet {
Ok(HistoryNote { Ok(HistoryNote {
id: row.get(0)?, id: row.get(0)?,
timestamp: row.get(3)?, timestamp: row.get(3)?,
set_id: row.get(1)?, deck_id: row.get(1)?,
word_id: row.get(2)?, word_id: row.get(2)?,
answer: row.get(4)?, answer: row.get(4)?,
before: row.get(5)?, before: row.get(5)?,
@@ -43,7 +43,7 @@ impl TableAccessQuerySet<HistoryNote> for HistoryQuerySet {
fn insert_params(entity: &HistoryNote) -> impl Params { fn insert_params(entity: &HistoryNote) -> impl Params {
( (
entity.set_id, entity.deck_id,
entity.word_id, entity.word_id,
entity.timestamp, entity.timestamp,
entity.answer, entity.answer,
@@ -55,7 +55,7 @@ impl TableAccessQuerySet<HistoryNote> for HistoryQuerySet {
fn update_params(entity: &HistoryNote) -> impl Params { fn update_params(entity: &HistoryNote) -> impl Params {
( (
entity.set_id, entity.deck_id,
entity.word_id, entity.word_id,
entity.timestamp, entity.timestamp,
entity.answer, entity.answer,
+1 -1
View File
@@ -51,7 +51,7 @@ impl HistoryState {
let history_repo = &mut state.history.write(); let history_repo = &mut state.history.write();
history_repo.init().expect("TODO: panic message"); history_repo.init().expect("TODO: panic message");
let id = id.clone(); let id = id.clone();
let mut history = history_repo.query(Arc::new(move |h| h.set_id == id)); let mut history = history_repo.query(Arc::new(move |h| h.deck_id == id));
let words = history let words = history
.iter(history_repo) .iter(history_repo)
.map( .map(
+75 -87
View File
@@ -2,11 +2,10 @@ use crate::app::AppState;
use crate::core::deck::DeckData; use crate::core::deck::DeckData;
use crate::core::srs::{AppendMode, OrderMode}; use crate::core::srs::{AppendMode, OrderMode};
use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData}; use crate::data::entities::{CardState, DeckSettings, EntityHandle, WordData};
use crate::data::repositories::Repository; use crate::data::repositories::{BatchedRepository, Repository};
use crate::data_provider::card_states::add_stat_list;
use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings}; use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings};
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::notification::{MaybeError, NotificationRequest}; use crate::notification::MaybeError;
use crate::pages::history::HistoryState; use crate::pages::history::HistoryState;
use crate::pages::repetition::RepetitionState; use crate::pages::repetition::RepetitionState;
use crate::pages::repetition_settings::RepetitionSettingsState; use crate::pages::repetition_settings::RepetitionSettingsState;
@@ -15,7 +14,7 @@ use crate::push_error;
use crate::push_error_task; use crate::push_error_task;
use crate::styling::*; use crate::styling::*;
use chrono::{Days, Local, Utc}; use chrono::{Days, Local, Utc};
use hashbrown::HashSet; use hashbrown::{HashMap, HashSet};
pub use iced::widget::button::{Catalog, Style}; pub use iced::widget::button::{Catalog, Style};
use iced::widget::button::{Status, danger}; use iced::widget::button::{Status, danger};
use iced::widget::tooltip::Position::Top; use iced::widget::tooltip::Position::Top;
@@ -33,7 +32,7 @@ use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
pub struct RepetitionsState { pub struct RepetitionsState {
selected_deck_handle: Option<EntityHandle<DeckSettings>>, selected_deck_handle: Option<EntityHandle<DeckSettings>>,
decks: Vec<DeckViewData>, decks: HashMap<EntityHandle<DeckSettings>, DeckViewData>,
state: AppState, state: AppState,
} }
@@ -82,12 +81,7 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
if let Some(index) = self.selected_deck_handle { if let Some(index) = self.selected_deck_handle {
let decks = self.state.decks.read(); let decks = self.state.decks.read();
if let None = decks.get_static(index) { if let None = decks.get_static(index) {
let position = self self.decks.remove(&index);
.decks
.iter()
.position(|deck_view_data: &DeckViewData| deck_view_data.id == index.id)
.unwrap();
self.decks.remove(position);
drop(decks); drop(decks);
self.clear_selection(); self.clear_selection();
} }
@@ -105,33 +99,32 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
CreateSet => { CreateSet => {
let index = self.decks.len() + 1; let index = self.decks.len() + 1;
self.decks.push(DeckViewData { let general = DeckSettings::with_name(format!("Колода #{}", index));
general_settings: DeckSettings::with_name(format!("Колода #{}", index)), let handle = self.state.decks.write().create(general.clone());
existing_words_indices: Default::default(), self.decks.insert(
available_words_indices: Default::default(), handle,
indices_ready: false, DeckViewData {
append_mode: Default::default(), settings_handle: handle,
count: None, general_settings: general,
open_mode: OrderMode::Default, existing_words_indices: Default::default(),
}); available_words_indices: Default::default(),
indices_ready: false,
append_mode: Default::default(),
count: None,
open_mode: OrderMode::Default,
},
);
} }
DeleteSet => { DeleteSet => {
let index = self.selected_deck_handle.unwrap(); let index = self.selected_deck_handle.unwrap();
self.clear_selection(); self.clear_selection();
let deck = self.decks.remove( let deck = self.decks.remove(&index);
self.decks
.iter()
.position(|dvd| dvd.id == index.id)
.unwrap(),
);
if index.real() { if index.real() {
let mut repo = self.state.decks.write(); let mut repo = self.state.decks.write();
if let Err(err) = repo.delete(index) { if let Err(err) = repo.delete(index) {
return push_error_task!(err); return push_error_task!(err);
} }
let index = self.decks.iter().position(|dvd| dvd.id == index.id);
self.decks.remove(index.unwrap());
} }
} }
SetName(value) => { SetName(value) => {
@@ -148,21 +141,14 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
} }
} }
Save => { Save => {
let mut deck = self.selected_deck().unwrap().clone(); let deck = self.selected_deck().unwrap().clone();
let handle = self.selected_deck_handle.unwrap(); let handle = self.selected_deck_handle.unwrap();
let mut repo = self.state.decks.write(); let mut repo = self.state.decks.write();
repo.set_static(handle, *deck); repo.set_static(handle, deck.general_settings.clone());
if let Err(err) = repo.save(&mut handle.clone()) { if let Err(err) = repo.save(&mut handle.clone()) {
return push_error_task!(err); return push_error_task!(err);
} }
if let Some(index) = deck.index {
state.decks[index] = deck.general_settings.clone();
} else {
state.decks.push(deck.general_settings.clone());
deck.index = Some(state.decks.len() - 1);
self.decks[self.selected_deck_handle.unwrap()] = deck.clone();
}
} }
SetForward(value) => { SetForward(value) => {
let deck = self.selected_deck_mut().unwrap(); let deck = self.selected_deck_mut().unwrap();
@@ -213,10 +199,10 @@ impl NavigatedPage<RepetitionsMessage> for RepetitionsState {
deck.existing_words_indices.append(&mut adding.clone()); deck.existing_words_indices.append(&mut adding.clone());
} }
let deck = self.selected_deck().unwrap(); let deck = self.selected_deck().unwrap().clone();
let mut state = self.state.lock(); let state = &mut self.state;
if let Err(err) = if let Err(err) =
Self::append_words(&mut state, &deck.general_settings, adding.into_iter()) Self::append_words(state, &deck.general_settings, adding.into_iter())
{ {
return push_error_task!(err); return push_error_task!(err);
} }
@@ -259,23 +245,28 @@ impl RepetitionsState {
} }
} }
fn get_decks_from_state(state: &AppState) -> Vec<DeckViewData> { fn get_decks_from_state(state: &AppState) -> HashMap<EntityHandle<DeckSettings>, DeckViewData> {
state state
.decks .decks
.read() .read()
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, deck)| DeckViewData { .map(|(index, deck)| {
general_settings: deck.clone(), (
existing_words_indices: Default::default(), EntityHandle::new(deck.id, index, true),
available_words_indices: Default::default(), DeckViewData {
indices_ready: false, settings_handle: EntityHandle::new(deck.id, index, true),
append_mode: Default::default(), general_settings: deck.clone(),
count: None, existing_words_indices: Default::default(),
index: Some(index), available_words_indices: Default::default(),
open_mode: OrderMode::Default, indices_ready: false,
append_mode: Default::default(),
count: None,
open_mode: OrderMode::Default,
},
)
}) })
.collect() .collect::<HashMap<_, _>>()
} }
} }
@@ -395,22 +386,20 @@ impl RepetitionsState {
fn activity_bar(&self, deck: &DeckSettings) -> Element<'_, RepetitionsMessage> { fn activity_bar(&self, deck: &DeckSettings) -> Element<'_, RepetitionsMessage> {
const MAX_DAY_COUNT: f32 = 128.0; const MAX_DAY_COUNT: f32 = 128.0;
let state = self.state.lock(); let history = self.state.history.read();
let history = state.activity.get(&deck.id); let history = history.activity_of_deck(deck.id);
let mut counts: Vec<u32> = vec![0; 30 * 7]; let mut counts: Vec<u32> = vec![0; 30 * 7];
let now = Local::now().date_naive(); let now = Local::now().date_naive();
if let Some(history) = history { for (date, activity) in history {
for (date, activity) in history { let distance = now - date;
let distance = now - *date; let index = counts.len() as i64 - distance.num_days() - 1;
let index = counts.len() as i64 - distance.num_days() - 1;
if index < 0 { if index < 0 {
continue; continue;
}
counts[index as usize] = *activity;
} }
counts[index as usize] = activity;
} }
lazy((counts, now), |(counts, now)| { lazy((counts, now), |(counts, now)| {
@@ -509,8 +498,8 @@ impl RepetitionsState {
let available = selected_deck.available_words_indices.len(); let available = selected_deck.available_words_indices.len();
text!("{} слова добавлено из {}", now, available).into() text!("{} слова добавлено из {}", now, available).into()
} }
fn count_view(&self, deck: &DeckSettings) -> Element<'_, RepetitionsMessage> { fn count_view(&self, deck: &DeckViewData) -> Element<'_, RepetitionsMessage> {
if let Some(count) = self.decks.iter().find() { if let Some(count) = deck.count {
return text!("Количество слов: {}", count).into(); return text!("Количество слов: {}", count).into();
} }
space().into() space().into()
@@ -518,10 +507,10 @@ impl RepetitionsState {
fn decks_list(&self) -> Column<'_, RepetitionsMessage> { fn decks_list(&self) -> Column<'_, RepetitionsMessage> {
let mut column = Column::new(); let mut column = Column::new();
for (i, deck) in self.decks.iter().enumerate() { for (i, (handle, deck)) in self.decks.iter().enumerate() {
column = column.push( column = column.push(
button(text!("{}", &deck.name)) button(text!("{}", &deck.name))
.on_press_with(move || SelectSet(i)) .on_press_with(move || SelectSet(*handle))
.style(move |_x: &Theme, status: Status| Style { .style(move |_x: &Theme, status: Status| Style {
background: if status == Status::Hovered { background: if status == Status::Hovered {
Some(Background::Color(Color::WHITE.scale_alpha(0.2))) Some(Background::Color(Color::WHITE.scale_alpha(0.2)))
@@ -566,38 +555,36 @@ impl RepetitionsState {
) -> MaybeError { ) -> MaybeError {
let words = &state.dictionary; let words = &state.dictionary;
let stats = &mut indices let stats = &mut indices
.map(|i| { .map(|i| CardState {
let word = &words[i]; id: 0.into(),
CardState { word_id: i.id,
id: 0.into(), last_open: Utc::now(),
word_id: word.id, score: 1,
last_open: Utc::now(), fsrs_opened: Utc::now().date_naive(),
score: 1, fsrs_memory: None,
fsrs_opened: Utc::now().date_naive(), deck_id: deck.id,
fsrs_memory: None, fsrs_open_day: Utc::now().date_naive(),
deck_id: deck.id,
fsrs_open_day: Utc::now().date_naive(),
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !stats.is_empty() { if !stats.is_empty() {
add_stat_list(stats, &mut state.connection)?; let mut repo = state.card_states.write();
repo.create_now_many(stats.as_mut_slice())?;
} }
Ok(()) Ok(())
} }
fn select_deck(&mut self, index: usize) { fn select_deck(&mut self, index: EntityHandle<DeckSettings>) {
if self.decks.get(index).is_some() { if self.decks.contains_key(&index) {
self.selected_deck_handle = Some(index); self.selected_deck_handle = Some(index);
} }
} }
fn selected_deck(&self) -> Option<&DeckViewData> { fn selected_deck(&self) -> Option<&DeckViewData> {
self.decks.get(self.selected_deck_handle?) self.decks.get(&self.selected_deck_handle?)
} }
fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> { fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> {
self.decks.get_mut(self.selected_deck_handle?) self.decks.get_mut(&self.selected_deck_handle?)
} }
fn clear_selection(&mut self) { fn clear_selection(&mut self) {
@@ -613,10 +600,10 @@ impl RepetitionsState {
.map(|state| EntityHandle::from_id(state.word_id)) .map(|state| EntityHandle::from_id(state.word_id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut deck = self.selected_deck().unwrap().clone(); let mut deck = self.selected_deck().unwrap().clone();
let total = DeckData::list(&deck, &self.state)?; let total = DeckData::list(&deck, &self.state);
deck.existing_words_indices = added; deck.existing_words_indices = added;
deck.available_words_indices = total; deck.available_words_indices = total;
self.decks[self.selected_deck_handle.unwrap()] = deck; self.decks.insert(self.selected_deck_handle.unwrap(), deck);
Ok(()) Ok(())
} }
} }
@@ -628,7 +615,7 @@ pub enum RepetitionsMessage {
CreateSet, CreateSet,
DeleteSet, DeleteSet,
SetName(String), SetName(String),
SelectSet(usize), SelectSet(EntityHandle<DeckSettings>),
Save, Save,
SetForward(String), SetForward(String),
SetBackward(String), SetBackward(String),
@@ -643,6 +630,7 @@ pub enum RepetitionsMessage {
#[derive(Clone)] #[derive(Clone)]
struct DeckViewData { struct DeckViewData {
settings_handle: EntityHandle<DeckSettings>,
general_settings: DeckSettings, general_settings: DeckSettings,
existing_words_indices: Vec<EntityHandle<WordData>>, existing_words_indices: Vec<EntityHandle<WordData>>,
available_words_indices: Vec<EntityHandle<WordData>>, available_words_indices: Vec<EntityHandle<WordData>>,