From 80785ff9e61bd9073aa6d1438337a7cfcc5f30da Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Tue, 18 Aug 2026 15:13:10 +0300 Subject: [PATCH] Rewrite repetitions page --- src/repetitions.rs | 314 ++++++++++++++++++++++++++++++++------------- 1 file changed, 227 insertions(+), 87 deletions(-) diff --git a/src/repetitions.rs b/src/repetitions.rs index a5e1fa0..2738866 100644 --- a/src/repetitions.rs +++ b/src/repetitions.rs @@ -1,8 +1,7 @@ -use std::ops::Deref; -use crate::data_provider::card_sets::{delete_set, update_card_set}; +use crate::data_provider::card_sets::update_card_set; use crate::data_provider::card_stats::{add_stat_list, load_stats_of_set}; use crate::history::HistoryState; -use crate::lang::{AppendMode, DeckSettings, CardStatistics, OrderMode}; +use crate::lang::{AppendMode, CardStatistics, DeckSettings, Id, OrderMode, INVALID_ID}; use crate::navigation::Page::{History, PreviousPage, Repetition, RepetitionSettings}; use crate::navigation::{NavigatedPage, Page}; use crate::repetition::RepetitionState; @@ -12,24 +11,25 @@ use crate::styling::*; use crate::{AppState, RootMessage}; use chrono::{Days, Local, Utc}; use hashbrown::{HashMap, HashSet}; +use iced::widget::button::{danger, Status}; pub use iced::widget::button::{Catalog, Style}; -use iced::widget::button::{Status, danger}; use iced::widget::tooltip::Position::Top; use iced::widget::{ - Column, Row, button, column, container, lazy, radio, row, scrollable, space, svg, text, - text_input, tooltip, + button, column, container, lazy, radio, row, scrollable, space, svg, text, text_input, tooltip, + Column, Row, }; use iced::{Background, Border, Center, Color, Element, Fill, Length, Shadow, Task, Theme}; -use iced_core::Padding; use iced_core::border::Radius; use iced_core::svg::Handle; +use iced_core::Padding; +use std::ops::Deref; use std::sync::{Arc, Mutex}; #[derive(Clone)] pub struct RepetitionsState { - word_id_index_map: HashMap, - view_data: Option, - decks: Vec, + word_id_index_map: HashMap, + selected_deck_index: Option, + decks: Vec, state: Arc>, } @@ -38,35 +38,42 @@ impl NavigatedPage for RepetitionsState { if let Back = message { Some(PreviousPage) } else if let GoToHistory = message { - let card_set; - { - card_set = self.state.lock().unwrap().card_sets[self.selected_set_index.unwrap()].id; - } - Some(History(HistoryState::new(card_set, self.state.clone()))) + Some(History(HistoryState::new( + self.selected_deck().unwrap().id, + self.state.clone(), + ))) } else if let GoToRepetition = message { let clone = self.state.clone(); - let card_set; - { - card_set = self.state.lock().unwrap().card_sets[self.selected_set_index.unwrap()].clone(); - } - if self.local_settings[&card_set.id].append_mode == AppendMode::Full { - self.append_all_words(&card_set); + let selected_set = self.selected_deck_mut().unwrap(); + if selected_set.append_mode == AppendMode::Full { + + Self::append_all_words(&clone.lock().unwrap(), selected_set); + selected_set.existing_words_indices = selected_set.available_words_indices.clone(); } else { - let cache = self - .current_sets_cards_cache - .get(&self.selected_set_index.unwrap()) - .unwrap(); - if cache.1.is_empty() || cache.0.is_empty() { - let local_settings = self.local_settings.get_mut(&card_set.id).unwrap(); - local_settings.append_warning = true; + if selected_set + .available_words_indices + .as_ref() + .unwrap() + .is_empty() + || selected_set + .existing_words_indices + .as_ref() + .unwrap() + .is_empty() + { + selected_set.append_warning = true; + return None; } } - Some(Repetition(RepetitionState::new(card_set, clone))) + Some(Repetition(RepetitionState::new( + selected_set.general_settings.clone(), + clone, + ))) } else if let GoToSettings = message { Some(RepetitionSettings(RepetitionSettingsState::new( - self.selected_set_index.unwrap(), + self.selected_deck().unwrap().index.unwrap(), self.state.clone(), ))) } else { @@ -74,8 +81,19 @@ impl NavigatedPage for RepetitionsState { } } fn navigated(&mut self) { - self.clear_selection(); - self.current_sets_cards_cache.clear(); + let index = self.selected_deck_index.unwrap(); + let state = self.state.lock().unwrap(); + if let Some(global_deck) = state.card_sets.get(index) { + if global_deck.id != self.selected_deck().unwrap().id { + drop(state); + self.decks.remove(index); + self.clear_selection(); + } + } else { + drop(state); + self.decks.remove(index); + self.clear_selection() + } } fn update(&mut self, message: RepetitionsMessage) -> Task { match message { @@ -86,19 +104,120 @@ impl NavigatedPage for RepetitionsState { GoToSettings => {} CreateSet => { - + let index = self.decks.len() + 1; + self.decks.push(DeckViewData { + general_settings: DeckSettings::with_name(format!("Колода #{}", index)), + existing_words_indices: None, + available_words_indices: None, + append_mode: Default::default(), + append_warning: false, + valid_filter: true, + index: None, + }); + } + DeleteSet => { + let index = self.selected_deck_index.unwrap(); + self.clear_selection(); + let deck = self.decks.remove(index); + if deck.id != INVALID_ID { + let mut state = self.state.lock().unwrap(); + state.card_sets.remove(index); + } + } + SetName(value) => { + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.name = value; + } + SelectSet(index) => { + self.select_set(index); + let deck = self.selected_deck().unwrap(); + if deck.existing_words_indices.is_none() { + let state = self.state.lock().unwrap(); + let added: Vec<_> = load_stats_of_set(deck, &state.connection) + .iter() + .map(|c| self.word_id_index_map[&c.word_id]) + .collect(); + let total = deck.get_word_list(&state); + drop(state); + let deck = self.selected_deck_mut().unwrap(); + deck.existing_words_indices = Some(added); + deck.available_words_indices = Some(total); + } + } + Save => { + let mut deck = self.selected_deck().unwrap().clone(); + let mut state = self.state.lock().unwrap(); + { + update_card_set(&mut deck.general_settings, &state.connection); + if let Some(index) = deck.index { + state.card_sets[index] = deck.general_settings.clone(); + } else { + state.card_sets.push(deck.general_settings.clone()); + deck.index = Some(state.card_sets.len() - 1); + self.decks[deck.index.unwrap()] = deck.clone(); + } + } + } + SetForward(value) => { + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.forward = value; + } + SetBackward(value) => { + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.backward = value; + } + SetFilter(value) => { + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.filter = value; + } + TryFilter => { + let state = self.state.lock().unwrap(); + let deck = self.selected_deck().unwrap(); + let count = deck.get_word_list(&state).len(); + drop(state); + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.count = Some(count); + } + SetOpenMode(mode) => { + let deck = self.selected_deck_mut().unwrap(); + deck.general_settings.open_mode = mode; + } + SetAppendMode(mode) => { + let deck = self.selected_deck_mut().unwrap(); + deck.append_mode = mode; + } + AppendWords(count) => { + let adding: Vec; + { + { + let deck = self.selected_deck().unwrap(); + let existing_words_indices = deck.existing_words_indices.as_ref().unwrap(); + let mut created_set = HashSet::with_capacity(existing_words_indices.len()); + existing_words_indices.iter().for_each(|c| { + created_set.insert(c); + }); + adding = deck + .available_words_indices + .as_ref() + .unwrap() + .iter() + .filter(|c| !created_set.contains(c)) + .take(count) + .cloned() + .collect(); + } + + let deck = self.selected_deck_mut().unwrap(); + deck.existing_words_indices + .as_mut() + .unwrap() + .append(&mut adding.clone()); + } + + let deck = self.selected_deck().unwrap(); + let state = self.state.lock().unwrap(); + Self::append_words(&state, &deck.general_settings, adding.into_iter()); } - DeleteSet => {} - SetName(_) => {} - SelectSet(_) => {} - Save => {} - SetForward(_) => {} - SetBackward(_) => {} - SetFilter(_) => {} - TryFilter => {} - SetOpenMode(_) => {} - SetAppendMode(_) => {} - AppendWords(_) => {} } Task::none() } @@ -130,7 +249,6 @@ impl NavigatedPage for RepetitionsState { impl RepetitionsState { pub(crate) fn new(state: Arc>) -> RepetitionsState { let state_ = state.lock().unwrap(); - let count = state_.card_sets.len(); let mut map = HashMap::with_capacity(state_.dictionary.len()); state_ @@ -141,27 +259,31 @@ impl RepetitionsState { map.insert(word.id, index); }); - let mut local_settings = HashMap::with_capacity(state_.card_sets.len()); - state_.card_sets.iter().for_each(|card_set| { - local_settings.insert(card_set.id, Default::default()); - }); + let decks = state_.card_sets.iter().enumerate().map(|(index, set)| {DeckViewData{ + general_settings: set.clone(), + existing_words_indices: None, + available_words_indices: None, + append_mode: Default::default(), + append_warning: false, + valid_filter: true, + index: Some(index), + }}).collect(); drop(state_); RepetitionsState { - selected_set_index: None, - correct_filters: vec![true; count], - current_sets_cards_cache: Default::default(), word_id_index_map: map, - local_settings, - view_data: None, + selected_deck_index: None, + decks, state, - set_names: vec![], } } } impl RepetitionsState { - fn launch_delete_button(&self, set: &DeckSettings) -> Element<'_, RepetitionsMessage> { - if set.id != 0 { + fn launch_delete_button(&self, set: &DeckViewData) -> Element<'_, RepetitionsMessage> { + if set.id != INVALID_ID { + if set.append_mode == AppendMode::Manual && set.existing_words_indices.as_ref().unwrap().is_empty() { + return text!("Добавьте карточки или измените фильтр").into(); + } return row![ space().width(Fill), button(text!("Начать повторение").width(200).center()) @@ -173,7 +295,7 @@ impl RepetitionsState { button("Удалить").style(danger).on_press(DeleteSet).into() } fn selected_set_view(&self) -> Element<'_, RepetitionsMessage> { - if let Some(set) = &self.view_data { + if let Some(set) = &self.selected_deck() { return column![ scrollable( column![ @@ -189,7 +311,7 @@ impl RepetitionsState { ] .spacing(QUARTER_SPACING), { - if set.id == 0 { + if set.id == INVALID_ID { column![ column![ text!("Передняя сторона"), @@ -252,9 +374,9 @@ impl RepetitionsState { .height(Fill), row![ button("Сохранить").style(jl_button).on_press(Save), - self.launch_delete_button(&set) + self.launch_delete_button(set) ] - .spacing(DEFAULT_SPACING), + .spacing(DEFAULT_SPACING).align_y(Center), ] .spacing(DEFAULT_SPACING) .width(Length::FillPortion(2)) @@ -345,20 +467,20 @@ impl RepetitionsState { }) .into() } - fn word_append_panel(&self, set: &DeckSettings) -> Element<'_, RepetitionsMessage> { + fn word_append_panel(&self, set: &DeckViewData) -> Element<'_, RepetitionsMessage> { column![ text!("Режим добавления карточек"), row![ radio( "Добавлять все доступные", AppendMode::Full, - Some(self.local_settings[&set.id].append_mode), + Some(set.append_mode), SetAppendMode ), radio( "Добавлять вручную", AppendMode::Manual, - Some(self.local_settings[&set.id].append_mode), + Some(set.append_mode), SetAppendMode ) ] @@ -368,8 +490,8 @@ impl RepetitionsState { .spacing(HALF_SPACING) .into() } - fn adder_panel(&self, set: &DeckSettings) -> Element<'_, RepetitionsMessage> { - match self.local_settings[&set.id].append_mode { + fn adder_panel(&self, set: &DeckViewData) -> Element<'_, RepetitionsMessage> { + match set.append_mode { AppendMode::Full => space().into(), AppendMode::Manual => row![ button("+5").on_press(AppendWords(5)), @@ -381,10 +503,15 @@ impl RepetitionsState { .into(), } } + fn count_comparator_view(&self) -> Element<'_, RepetitionsMessage> { - let cache = &self.current_sets_cards_cache[&self.selected_set_index.unwrap()]; - let now = cache.0.len(); - let available = cache.1.len(); + let selected_deck = self.selected_deck().unwrap(); + let now = selected_deck.existing_words_indices.as_ref().unwrap().len(); + let available = selected_deck + .available_words_indices + .as_ref() + .unwrap() + .len(); text!("{} слова добавлено из {}", now, available).into() } fn count_view(&self, set: &DeckSettings) -> Element<'_, RepetitionsMessage> { @@ -396,9 +523,9 @@ impl RepetitionsState { fn sets_list(&self) -> Column<'_, RepetitionsMessage> { let mut column = Column::new(); - for (i, set) in self.set_names.iter().enumerate() { + for (i, set) in self.decks.iter().enumerate() { column = column.push( - button(text!("{}", set.clone())) + button(text!("{}", &set.name)) .on_press_with(move || SelectSet(i)) .style(move |_x: &Theme, status: Status| Style { background: if status == Status::Hovered { @@ -406,7 +533,7 @@ impl RepetitionsState { } else { None }, - text_color: if self.correct_filters[i] { + text_color: if set.valid_filter { _x.palette().primary } else { _x.palette().warning @@ -427,14 +554,20 @@ impl RepetitionsState { } impl RepetitionsState { - fn append_all_words(&self, set: &DeckSettings) { - let cache = &self.current_sets_cards_cache[&index]; - let mut created_set = HashSet::with_capacity(cache.0.len()); - cache.0.iter().for_each(|c| { + fn append_all_words(state: &AppState, deck: &DeckViewData) { + let existing = deck.existing_words_indices.as_ref().unwrap(); + let mut created_set = HashSet::with_capacity(existing.len()); + existing.iter().for_each(|c| { created_set.insert(c); }); - let required = cache.1.iter().filter(|i| !created_set.contains(i)).cloned(); - Self::append_words(&self.state.lock().unwrap(), set, required); + let required = deck + .available_words_indices + .as_ref() + .unwrap() + .iter() + .filter(|i| !created_set.contains(i)) + .cloned(); + Self::append_words(state, deck, required); } fn append_words(state: &AppState, set: &DeckSettings, indices: impl Iterator) { let words = &state.dictionary; @@ -442,7 +575,7 @@ impl RepetitionsState { .map(|i| { let word = words.get(i).unwrap(); CardStatistics { - id: 0, + id: 0.into(), word_id: word.id, last_open: Utc::now(), score: 1, @@ -455,15 +588,22 @@ impl RepetitionsState { add_stat_list(stats, &state.connection); } } - fn select_set(&mut self, index: usize) { - let state = self.state.lock().unwrap(); - if let Some(set) = state.card_sets.get(index) { + fn select_set(&mut self, index: usize) { + if self.decks.get(index).is_some() { + self.selected_deck_index = Some(index); } } + fn selected_deck(&self) -> Option<&DeckViewData> { + self.decks.get(self.selected_deck_index?) + } + fn selected_deck_mut(&mut self) -> Option<&mut DeckViewData> { + self.decks.get_mut(self.selected_deck_index?) + } + fn clear_selection(&mut self) { - self.view_data = None; + self.selected_deck_index = None; } } #[derive(Clone)] @@ -489,18 +629,18 @@ pub enum RepetitionsMessage { #[derive(Clone)] struct DeckViewData { - set_settings: DeckSettings, + general_settings: DeckSettings, existing_words_indices: Option>, available_words_indices: Option>, append_mode: AppendMode, append_warning: bool, valid_filter: bool, - index: usize, + index: Option, } impl Deref for DeckViewData { type Target = DeckSettings; fn deref(&self) -> &Self::Target { - &self.set_settings + &self.general_settings } -} \ No newline at end of file +}