mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Allow user to configure hard/good buttons when rescheduling off
Closes #2858
This commit is contained in:
parent
fdcdc14f6b
commit
e778cba089
13 changed files with 163 additions and 155 deletions
|
@ -25,7 +25,9 @@ decks-order-due = Order due
|
|||
decks-please-select-something = Please select something.
|
||||
decks-random = Random
|
||||
decks-relative-overdueness = Relative overdueness
|
||||
decks-repeat-failed-cards-after = Repeat failed cards after
|
||||
decks-repeat-failed-cards-after = Delay Repeat failed cards after
|
||||
# e.g. "Delay for Again", "Delay for Hard", "Delay for Good"
|
||||
decks-delay-for-button = Delay for { $button }
|
||||
decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answers in this deck
|
||||
decks-study = Study
|
||||
decks-study-deck = Study Deck
|
||||
|
|
|
@ -110,7 +110,11 @@ message Deck {
|
|||
// v1 scheduler only
|
||||
repeated float delays = 3;
|
||||
// v2 scheduler only
|
||||
uint32 preview_delay = 4;
|
||||
uint32 preview_again_mins = 4;
|
||||
// recent v3 scheduler only; 0 means card will be returned
|
||||
uint32 preview_hard_mins = 5;
|
||||
// recent v3 scheduler only; 0 means card will be returned
|
||||
uint32 preview_good_mins = 6;
|
||||
}
|
||||
// a container to store the deck specifics in the DB
|
||||
// as a tagged enum
|
||||
|
|
|
@ -740,7 +740,7 @@ def test_preview():
|
|||
|
||||
passing_grade = 4
|
||||
assert col.sched.answerButtons(c) == passing_grade
|
||||
assert col.sched.nextIvl(c, 1) == 600
|
||||
assert col.sched.nextIvl(c, 1) == 60
|
||||
assert col.sched.nextIvl(c, passing_grade) == 0
|
||||
|
||||
# failing it will push its due time back
|
||||
|
|
|
@ -100,6 +100,16 @@ class FilteredDeckConfigDialog(QDialog):
|
|||
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.FILTERED_DECK)
|
||||
)
|
||||
|
||||
self.form.again_delay_label.setText(
|
||||
tr.decks_delay_for_button(button=tr.studying_again())
|
||||
)
|
||||
self.form.hard_delay_label.setText(
|
||||
tr.decks_delay_for_button(button=tr.studying_hard())
|
||||
)
|
||||
self.form.good_delay_label.setText(
|
||||
tr.decks_delay_for_button(button=tr.studying_good())
|
||||
)
|
||||
|
||||
restoreGeom(self, self.GEOMETRY_KEY)
|
||||
|
||||
def load_deck_and_show(self, deck: FilteredDeckForUpdate) -> None:
|
||||
|
@ -132,10 +142,9 @@ class FilteredDeckConfigDialog(QDialog):
|
|||
form.order.setCurrentIndex(term1.order)
|
||||
form.limit.setValue(term1.limit)
|
||||
|
||||
form.steps.setVisible(False)
|
||||
form.stepsOn.setVisible(False)
|
||||
|
||||
form.previewDelay.setValue(config.preview_delay)
|
||||
form.preview_again.setValue(config.preview_again_mins)
|
||||
form.preview_hard.setValue(config.preview_hard_mins)
|
||||
form.preview_good.setValue(config.preview_good_mins)
|
||||
|
||||
if len(config.search_terms) > 1:
|
||||
term2: FilteredDeckConfig.SearchTerm = config.search_terms[1]
|
||||
|
@ -268,7 +277,9 @@ class FilteredDeckConfigDialog(QDialog):
|
|||
|
||||
del config.search_terms[:]
|
||||
config.search_terms.extend(terms)
|
||||
config.preview_delay = form.previewDelay.value()
|
||||
config.preview_again_mins = form.preview_again.value()
|
||||
config.preview_hard_mins = form.preview_hard.value()
|
||||
config.preview_good_mins = form.preview_good.value()
|
||||
|
||||
return True
|
||||
|
||||
|
@ -293,32 +304,3 @@ class FilteredDeckConfigDialog(QDialog):
|
|||
add_or_update_filtered_deck(parent=self, deck=self.deck).success(
|
||||
success
|
||||
).run_in_background()
|
||||
|
||||
# Step load/save
|
||||
########################################################
|
||||
# fixme: remove once we drop support for v1
|
||||
|
||||
def listToUser(self, values: list[Union[float, int]]) -> str:
|
||||
return " ".join(
|
||||
[str(int(val)) if int(val) == val else str(val) for val in values]
|
||||
)
|
||||
|
||||
def userToList(self, line: QLineEdit, minSize: int = 1) -> list[float] | None:
|
||||
items = str(line.text()).split(" ")
|
||||
ret = []
|
||||
for item in items:
|
||||
if not item:
|
||||
continue
|
||||
try:
|
||||
i = float(item)
|
||||
if i <= 0:
|
||||
raise Exception("0 invalid")
|
||||
ret.append(i)
|
||||
except:
|
||||
# invalid, don't update
|
||||
showWarning(tr.scheduling_steps_must_be_numbers())
|
||||
return None
|
||||
if len(ret) < minSize:
|
||||
showWarning(tr.scheduling_at_least_one_step_is_required())
|
||||
return None
|
||||
return ret
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>757</width>
|
||||
<height>589</height>
|
||||
<width>526</width>
|
||||
<height>567</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -198,13 +198,67 @@
|
|||
<string>actions_options</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2" alignment="Qt::AlignLeft">
|
||||
<widget class="QCheckBox" name="resched">
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="previewDelayWidget" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="good_delay_label">
|
||||
<property name="text">
|
||||
<string notr="true">good delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QSpinBox" name="preview_again"/>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="preview_hard"/>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="again_delay_label">
|
||||
<property name="text">
|
||||
<string notr="true">again delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="preview_good"/>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>decks_minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3" colspan="2">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>decks_minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="hard_delay_label">
|
||||
<property name="text">
|
||||
<string notr="true">hard delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>decks_minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" alignment="Qt::AlignLeft">
|
||||
<widget class="QCheckBox" name="allow_empty">
|
||||
<property name="text">
|
||||
<string>decks_reschedule_cards_based_on_my_answers</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
<string>decks_create_even_if_empty</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -215,50 +269,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="previewDelayWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="previewDelayBox">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>decks_repeat_failed_cards_after</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="previewDelay"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>decks_minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLineEdit" name="steps">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2" alignment="Qt::AlignLeft">
|
||||
<widget class="QCheckBox" name="resched">
|
||||
<property name="text">
|
||||
<string notr="true">1 10</string>
|
||||
<string>decks_reschedule_cards_based_on_my_answers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" alignment="Qt::AlignLeft">
|
||||
<widget class="QCheckBox" name="stepsOn">
|
||||
<property name="text">
|
||||
<string>decks_custom_steps_in_minutes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" alignment="Qt::AlignLeft">
|
||||
<widget class="QCheckBox" name="allow_empty">
|
||||
<property name="text">
|
||||
<string>decks_create_even_if_empty</string>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -331,19 +348,18 @@
|
|||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>name</tabstop>
|
||||
<tabstop>search_button</tabstop>
|
||||
<tabstop>search</tabstop>
|
||||
<tabstop>limit</tabstop>
|
||||
<tabstop>order</tabstop>
|
||||
<tabstop>search_button_2</tabstop>
|
||||
<tabstop>search_2</tabstop>
|
||||
<tabstop>limit_2</tabstop>
|
||||
<tabstop>order_2</tabstop>
|
||||
<tabstop>resched</tabstop>
|
||||
<tabstop>previewDelay</tabstop>
|
||||
<tabstop>preview_again</tabstop>
|
||||
<tabstop>preview_hard</tabstop>
|
||||
<tabstop>preview_good</tabstop>
|
||||
<tabstop>secondFilter</tabstop>
|
||||
<tabstop>stepsOn</tabstop>
|
||||
<tabstop>steps</tabstop>
|
||||
<tabstop>allow_empty</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
@ -395,21 +411,5 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>stepsOn</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>steps</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>194</x>
|
||||
<y>351</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>190</x>
|
||||
<y>378</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
|
|
@ -22,7 +22,8 @@ impl Deck {
|
|||
limit: 20,
|
||||
order: FilteredSearchOrder::Due as i32,
|
||||
});
|
||||
filt.preview_delay = 10;
|
||||
filt.preview_again_mins = 1;
|
||||
filt.preview_hard_mins = 10;
|
||||
filt.reschedule = true;
|
||||
Deck {
|
||||
id: DeckId(0),
|
||||
|
|
|
@ -156,8 +156,12 @@ pub struct FilteredDeckSchema11 {
|
|||
delays: Option<Vec<f32>>,
|
||||
|
||||
// new scheduler
|
||||
#[serde(default, rename = "previewDelay")]
|
||||
preview_again_mins: u32,
|
||||
#[serde(default)]
|
||||
preview_delay: u32,
|
||||
preview_hard_mins: u32,
|
||||
#[serde(default)]
|
||||
preview_good_mins: u32,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Default, Clone)]
|
||||
pub struct DeckTodaySchema11 {
|
||||
|
@ -328,7 +332,9 @@ impl From<FilteredDeckSchema11> for FilteredDeck {
|
|||
reschedule: deck.resched,
|
||||
search_terms: deck.terms.into_iter().map(Into::into).collect(),
|
||||
delays: deck.delays.unwrap_or_default(),
|
||||
preview_delay: deck.preview_delay,
|
||||
preview_again_mins: deck.preview_again_mins,
|
||||
preview_hard_mins: deck.preview_hard_mins,
|
||||
preview_good_mins: deck.preview_good_mins,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +373,9 @@ impl From<Deck> for DeckSchema11 {
|
|||
} else {
|
||||
Some(filt.delays.clone())
|
||||
},
|
||||
preview_delay: filt.preview_delay,
|
||||
preview_again_mins: filt.preview_again_mins,
|
||||
preview_hard_mins: filt.preview_hard_mins,
|
||||
preview_good_mins: filt.preview_good_mins,
|
||||
common: deck.into(),
|
||||
}),
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ impl CardStateUpdater {
|
|||
.into()
|
||||
} else {
|
||||
PreviewState {
|
||||
scheduled_secs: filtered.preview_delay * 60,
|
||||
scheduled_secs: filtered.preview_again_mins * 60,
|
||||
finished: false,
|
||||
}
|
||||
.into()
|
||||
|
|
|
@ -91,10 +91,14 @@ impl CardStateUpdater {
|
|||
lapse_multiplier: self.config.inner.lapse_multiplier,
|
||||
minimum_lapse_interval: self.config.inner.minimum_lapse_interval,
|
||||
in_filtered_deck: self.deck.is_filtered(),
|
||||
preview_step: if let DeckKind::Filtered(deck) = &self.deck.kind {
|
||||
deck.preview_delay
|
||||
preview_delays: if let DeckKind::Filtered(deck) = &self.deck.kind {
|
||||
PreviewDelays {
|
||||
again: deck.preview_again_mins,
|
||||
hard: deck.preview_hard_mins,
|
||||
good: deck.preview_good_mins,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
Default::default()
|
||||
},
|
||||
fsrs_next_states: self.fsrs_next_states.clone(),
|
||||
}
|
||||
|
@ -185,6 +189,13 @@ impl CardStateUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct PreviewDelays {
|
||||
pub again: u32,
|
||||
pub hard: u32,
|
||||
pub good: u32,
|
||||
}
|
||||
|
||||
impl Rating {
|
||||
fn as_number(self) -> u8 {
|
||||
match self {
|
||||
|
|
|
@ -79,6 +79,13 @@ mod test {
|
|||
finished: true
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
next.good,
|
||||
CardState::Filtered(FilteredState::Preview(PreviewState {
|
||||
scheduled_secs: 0,
|
||||
finished: true
|
||||
}))
|
||||
));
|
||||
|
||||
// use Again on the preview
|
||||
col.answer_card(&mut CardAnswer {
|
||||
|
@ -108,7 +115,8 @@ mod test {
|
|||
c = col.storage.get_card(c.id)?.unwrap();
|
||||
assert_eq!(c.queue, CardQueue::PreviewRepeat);
|
||||
|
||||
// good
|
||||
// and then it should return to its old state once good or easy selected,
|
||||
// with the default filtered config
|
||||
let next = col.get_scheduling_states(c.id)?;
|
||||
col.answer_card(&mut CardAnswer {
|
||||
card_id: c.id,
|
||||
|
@ -120,20 +128,6 @@ mod test {
|
|||
custom_data: None,
|
||||
})?;
|
||||
c = col.storage.get_card(c.id)?.unwrap();
|
||||
assert_eq!(c.queue, CardQueue::PreviewRepeat);
|
||||
|
||||
// and then it should return to its old state once easy selected
|
||||
let next = col.get_scheduling_states(c.id)?;
|
||||
col.answer_card(&mut CardAnswer {
|
||||
card_id: c.id,
|
||||
current_state: next.current,
|
||||
new_state: next.easy,
|
||||
rating: Rating::Easy,
|
||||
answered_at: TimestampMillis::now(),
|
||||
milliseconds_taken: 0,
|
||||
custom_data: None,
|
||||
})?;
|
||||
c = col.storage.get_card(c.id)?.unwrap();
|
||||
assert_eq!(c.queue, CardQueue::DayLearn);
|
||||
assert_eq!(c.due, 123);
|
||||
|
||||
|
|
|
@ -208,7 +208,9 @@ fn custom_study_config(
|
|||
order: order as i32,
|
||||
}],
|
||||
delays: vec![],
|
||||
preview_delay: 10,
|
||||
preview_again_mins: 1,
|
||||
preview_hard_mins: 10,
|
||||
preview_good_mins: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ pub use review::ReviewState;
|
|||
|
||||
use self::steps::LearningSteps;
|
||||
use crate::revlog::RevlogReviewKind;
|
||||
use crate::scheduler::answering::PreviewDelays;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CardState {
|
||||
|
@ -106,7 +107,7 @@ pub(crate) struct StateContext<'a> {
|
|||
|
||||
// filtered
|
||||
pub in_filtered_deck: bool,
|
||||
pub preview_step: u32,
|
||||
pub preview_delays: PreviewDelays,
|
||||
}
|
||||
|
||||
impl<'a> StateContext<'a> {
|
||||
|
@ -136,7 +137,11 @@ impl<'a> StateContext<'a> {
|
|||
lapse_multiplier: 0.0,
|
||||
minimum_lapse_interval: 1,
|
||||
in_filtered_deck: false,
|
||||
preview_step: 10,
|
||||
preview_delays: PreviewDelays {
|
||||
again: 1,
|
||||
hard: 10,
|
||||
good: 0,
|
||||
},
|
||||
fsrs_next_states: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use super::CardState;
|
||||
use super::IntervalKind;
|
||||
use super::SchedulingStates;
|
||||
use super::StateContext;
|
||||
|
@ -24,27 +25,25 @@ impl PreviewState {
|
|||
pub(crate) fn next_states(self, ctx: &StateContext) -> SchedulingStates {
|
||||
SchedulingStates {
|
||||
current: self.into(),
|
||||
again: PreviewState {
|
||||
scheduled_secs: ctx.preview_step * 60,
|
||||
..self
|
||||
}
|
||||
.into(),
|
||||
hard: PreviewState {
|
||||
// ~15 minutes with the default setting
|
||||
scheduled_secs: ctx.preview_step * 90,
|
||||
..self
|
||||
}
|
||||
.into(),
|
||||
good: PreviewState {
|
||||
scheduled_secs: ctx.preview_step * 120,
|
||||
..self
|
||||
}
|
||||
.into(),
|
||||
easy: PreviewState {
|
||||
scheduled_secs: 0,
|
||||
finished: true,
|
||||
}
|
||||
.into(),
|
||||
again: delay_or_return(ctx.preview_delays.again),
|
||||
hard: delay_or_return(ctx.preview_delays.hard),
|
||||
good: delay_or_return(ctx.preview_delays.good),
|
||||
easy: delay_or_return(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delay_or_return(minutes: u32) -> CardState {
|
||||
if minutes == 0 {
|
||||
PreviewState {
|
||||
scheduled_secs: 0,
|
||||
finished: true,
|
||||
}
|
||||
} else {
|
||||
PreviewState {
|
||||
scheduled_secs: minutes * 60,
|
||||
finished: false,
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue