Study screen now matches the provided mockup's layout: a "Current Deck" panel with a circular mastery-progress ring, and a "Spaced Repetition" panel (New/Due/Learning/Review counts, streak, daily goal) on either side of the card. Kept the app's real global sidebar rather than the mockup's own nav, per direction. Mastery/Streak/Daily Goal weren't tracked anywhere, so this adds real backend support rather than showing fabricated numbers: - Mastered = graduated to review state AND FSRS stability >= 21 days (FlashcardScheduleStore.MasteryStabilityDaysThreshold), stricter than the plain Review-state count - CurrentStreakDays = consecutive UTC days with a review, computed from review-log timestamps in the new pure FlashcardScheduling .ComputeStreakDays (unit tested, habit-streak semantics: doesn't reset to 0 the instant a new day starts before today's first review) - DailyGoal/ReviewsToday reuse the account's existing ReviewsPerDay setting rather than inventing a new one Also removes the exit "X" button in favor of the same Escape/pause menu the question-player flow uses (main.gd's shared app_menu, with an "Exit Study Session" action), and adds a new circular_progress.gd reusable ring control plus a real "Options" menu (bury/suspend the current card) wired to schedule endpoints that had no UI before.
36 lines
1.6 KiB
C#
36 lines
1.6 KiB
C#
namespace QuestionsApi.Services.Flashcards;
|
|
|
|
// Small pure helpers pulled out of FlashcardStudyService/FlashcardScheduleStore so the
|
|
// daily-limit and sibling-bury rules can be unit tested without a live Mongo instance.
|
|
public static class FlashcardScheduling
|
|
{
|
|
// A card reviewed on a given local study day buries its siblings until the next local day.
|
|
public static DateTime NextStudyDayStart(DateTime studyDayStartUtc) => studyDayStartUtc.Date.AddDays(1);
|
|
|
|
// Effective per-day budget: deck override wins over the global default, minus what's
|
|
// already been consumed on the client's current local study day.
|
|
public static int ResolveDailyBudget(int globalLimit, int? deckOverride, int consumedToday) =>
|
|
Math.Max(0, (deckOverride ?? globalLimit) - consumedToday);
|
|
|
|
// A streak counts consecutive calendar days with at least one review, ending either today or
|
|
// yesterday (so the streak doesn't visibly reset to 0 the moment a new day starts before
|
|
// today's first review) - the same semantics as most habit-streak trackers.
|
|
public static int ComputeStreakDays(IReadOnlyCollection<DateTime> reviewTimestamps, DateTime todayUtc)
|
|
{
|
|
var reviewDays = reviewTimestamps.Select(v => v.Date).ToHashSet();
|
|
var cursor = todayUtc.Date;
|
|
if (!reviewDays.Contains(cursor))
|
|
{
|
|
cursor = cursor.AddDays(-1);
|
|
if (!reviewDays.Contains(cursor)) return 0;
|
|
}
|
|
var streak = 0;
|
|
while (reviewDays.Contains(cursor))
|
|
{
|
|
streak++;
|
|
cursor = cursor.AddDays(-1);
|
|
}
|
|
return streak;
|
|
}
|
|
}
|