diff --git a/proto/backend.proto b/proto/backend.proto index 4495a7fe8..26a171517 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -46,6 +46,7 @@ message BackendInput { TranslateStringIn translate_string = 30; FormatTimeSpanIn format_time_span = 31; StudiedTodayIn studied_today = 32; + CongratsLearnMsgIn congrats_learn_msg = 33; } } @@ -68,6 +69,7 @@ message BackendOutput { string translate_string = 30; string format_time_span = 31; string studied_today = 32; + string congrats_learn_msg = 33; BackendError error = 2047; } @@ -322,3 +324,8 @@ message StudiedTodayIn { uint32 cards = 1; double seconds = 2; } + +message CongratsLearnMsgIn { + float next_due = 1; + uint32 remaining = 2; +} \ No newline at end of file diff --git a/pylib/anki/rsbackend.py b/pylib/anki/rsbackend.py index a7993f8b5..f86065c66 100644 --- a/pylib/anki/rsbackend.py +++ b/pylib/anki/rsbackend.py @@ -362,3 +362,12 @@ class RustBackend: studied_today=pb.StudiedTodayIn(cards=cards, seconds=seconds) ) ).studied_today + + def learning_congrats_msg(self, next_due: float, remaining: int) -> str: + return self._run_command( + pb.BackendInput( + congrats_learn_msg=pb.CongratsLearnMsgIn( + next_due=next_due, remaining=remaining + ) + ) + ).congrats_learn_msg diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index f96150c4c..e876272bb 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -1458,8 +1458,29 @@ where id = ? + self._nextDueMsg() ) + def next_learn_msg(self) -> str: + dids = self._deckLimit() + (next, remaining) = self.col.db.first( + f""" +select min(due), count(*) +from cards where did in {dids} and queue = {QUEUE_TYPE_LRN} +""" + ) + next = next or 0 + remaining = remaining or 0 + if next and next < self.dayCutoff: + next -= intTime() - self.col.conf["collapseTime"] + return self.col.backend.learning_congrats_msg(abs(next), remaining) + else: + return "" + def _nextDueMsg(self) -> str: line = [] + + learn_msg = self.next_learn_msg() + if learn_msg: + line.append(learn_msg) + # the new line replacements are so we don't break translations # in a point release if self.revDue(): diff --git a/rslib/src/backend.rs b/rslib/src/backend.rs index 1cc63729f..f8dcea51c 100644 --- a/rslib/src/backend.rs +++ b/rslib/src/backend.rs @@ -11,7 +11,7 @@ use crate::media::check::MediaChecker; use crate::media::sync::MediaSyncProgress; use crate::media::MediaManager; use crate::sched::cutoff::{local_minutes_west_for_stamp, sched_timing_today}; -use crate::sched::timespan::{answer_button_time, studied_today, time_span}; +use crate::sched::timespan::{answer_button_time, learning_congrats, studied_today, time_span}; use crate::template::{ render_card, without_legacy_template_directives, FieldMap, FieldRequirements, ParsedTemplate, RenderedNode, @@ -207,6 +207,11 @@ impl Backend { input.seconds as f32, &self.i18n, )), + Value::CongratsLearnMsg(input) => OValue::CongratsLearnMsg(learning_congrats( + input.remaining as usize, + input.next_due, + &self.i18n, + )), }) } diff --git a/rslib/src/i18n/scheduling.ftl b/rslib/src/i18n/scheduling.ftl index f671abffd..a521bd9d7 100644 --- a/rslib/src/i18n/scheduling.ftl +++ b/rslib/src/i18n/scheduling.ftl @@ -44,3 +44,27 @@ time-span-years = { $amount -> [one] {$amount} year *[other] {$amount} years } + +## Shown in the "Congratulations!" message after study finishes. + +# eg "The next learning card will be ready in 5 minutes." +next-learn-due = + The next learning card will be ready in { $unit -> + [seconds] { $amount -> + [one] {$amount} second + *[other] {$amount} seconds + } + [minutes] { $amount -> + [one] {$amount} minute + *[other] {$amount} minutes + } + *[hours] { $amount -> + [one] {$amount} hour + *[other] {$amount} hours + } + }. + +learn-remaining = { $remaining -> + [one] There is one remaining learning card due later today. + *[other] There are {$remaining} learning cards due later today. + } diff --git a/rslib/src/i18n/statistics.ftl b/rslib/src/i18n/statistics.ftl index 792faaf16..c2b5733e6 100644 --- a/rslib/src/i18n/statistics.ftl +++ b/rslib/src/i18n/statistics.ftl @@ -12,7 +12,7 @@ cards-per-min = {$cards-per-minute} cards/minute average-answer-time = {$average-seconds}s ({cards-per-min}) ## A span of time studying took place in, for example -## "(studied 30 cards) in 3 minutes". +## "(studied 30 cards) in 3 minutes" in-time-span-seconds = { $amount -> [one] in {$amount} second diff --git a/rslib/src/sched/timespan.rs b/rslib/src/sched/timespan.rs index f1b1f7530..b609feef9 100644 --- a/rslib/src/sched/timespan.rs +++ b/rslib/src/sched/timespan.rs @@ -44,6 +44,27 @@ pub fn studied_today(cards: usize, secs: f32, i18n: &I18n) -> String { .trn("studied-today", args) } +// fixme: this doesn't belong here +pub fn learning_congrats(remaining: usize, next_due: f32, i18n: &I18n) -> String { + // next learning card not due (/ until tomorrow)? + if next_due == 0.0 || next_due >= 86_400.0 { + return "".to_string(); + } + + let span = Timespan::from_secs(next_due).natural_span(); + let amount = span.as_unit().round(); + let unit = span.unit().as_str(); + let next_args = tr_args!["amount" => amount, "unit" => unit]; + let remaining_args = tr_args!["remaining" => remaining]; + format!( + "{} {}", + i18n.get(StringsGroup::Scheduling) + .trn("next-learn-due", next_args), + i18n.get(StringsGroup::Scheduling) + .trn("learn-remaining", remaining_args) + ) +} + const SECOND: f32 = 1.0; const MINUTE: f32 = 60.0 * SECOND; const HOUR: f32 = 60.0 * MINUTE; @@ -134,7 +155,9 @@ impl Timespan { #[cfg(test)] mod test { use crate::i18n::I18n; - use crate::sched::timespan::{answer_button_time, studied_today, time_span, MONTH}; + use crate::sched::timespan::{ + answer_button_time, learning_congrats, studied_today, time_span, MONTH, + }; #[test] fn answer_buttons() { @@ -158,7 +181,11 @@ mod test { let i18n = I18n::new(&["zz"], ""); assert_eq!( &studied_today(3, 13.0, &i18n).replace("\n", " "), - "Studied 3 cards in 13 seconds today (4.33s/card)." + "Studied 3 cards in 13 seconds today (4.33s/card)" + ); + assert_eq!( + &learning_congrats(3, 3700.0, &i18n).replace("\n", " "), + "The next learning card will be ready in 1 hour. There are 3 learning cards due later today." ); } }