Skip to content

Comments

feat(reminders): only notify if no reviews#19157

Merged
david-allison merged 3 commits intoankidroid:mainfrom
ericli3690:ericli3690-review-reminders-only-notify-if-no-reviews
Jan 7, 2026
Merged

feat(reminders): only notify if no reviews#19157
david-allison merged 3 commits intoankidroid:mainfrom
ericli3690:ericli3690-review-reminders-only-notify-if-no-reviews

Conversation

@ericli3690
Copy link
Member

@ericli3690 ericli3690 commented Aug 29, 2025

Purpose / Description

Adds an advanced review reminder option. When this setting is enabled, the review reminder only triggers notifications if the deck the review reminder is for has not been reviewed yet today.

Adds a checkbox to the AddEditReminderDialog to toggle this setting on or off. Adds a method to NotificationService to check if a deck (or all decks) have been reviewed yet today. The check is accomplished via a database query of the revlog and cards tables. In my experience there is no noticeable latency, the query I've written should be fairly efficient.

UI

image

Fixes

  • GSoC 2025: Review Reminders

Approach

The SQL query is:

SELECT EXISTS (
    SELECT 1
    FROM cards
    JOIN revlog ON revlog.cid = cards.id
    WHERE revlog.id > $startOfToday
    AND cards.did = ${scope.did} -- this line is removed if the scope is Global
)

Where startOfToday is calculated via sched.dayCutoff - 1.days.inWholeSeconds.

How Has This Been Tested?

  • Unit tests pass.
  • Builds and runs on a physical Samsung S23, API 34.
  • Based on my own personal testing, it seems the feature works! I created a few review reminders, reviewed a few cards, etc.

Learning (optional, can help others)

I found the database schema wiki page useful: https://github.com/ankidroid/Anki-Android/wiki/Database-Structure

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@ericli3690 ericli3690 added Needs Review GSoC Pull requests authored by a Google Summer of Code participant [Candidate/Selected], for GSoC mentors Blocked by dependency Currently blocked by some other dependent / related change labels Aug 29, 2025
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 9f44e2d to 787f50a Compare September 6, 2025 05:19
@ericli3690
Copy link
Member Author

  • Rebased.

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 787f50a to f8c1584 Compare September 7, 2025 17:27
@ericli3690
Copy link
Member Author

  • Rebased.

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from f8c1584 to 5b3ac81 Compare September 9, 2025 05:56
@ericli3690
Copy link
Member Author

  • Rebased.

@ericli3690 ericli3690 marked this pull request as draft September 26, 2025 04:11
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 5b3ac81 to 1178a0e Compare November 25, 2025 20:00
@ericli3690 ericli3690 removed Blocked by dependency Currently blocked by some other dependent / related change Has Conflicts labels Nov 25, 2025
@ericli3690
Copy link
Member Author

Rebased, fully unblocked, and ready for review!

@ericli3690 ericli3690 marked this pull request as ready for review November 25, 2025 20:23
Copy link
Contributor

@criticalAY criticalAY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to add, LGTM!

Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks GREAT!

One request on the SQL concatenation

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 1178a0e to f2d2cce Compare December 26, 2025 01:09
@ericli3690
Copy link
Member Author

While addressing David's feedback (thanks David!) I found a very embarrassing bug where it turns out the feature... didn't work? I could've sworn it worked in my testing when I first pushed the PR, but it seems I was comparing seconds to milliseconds the entire time, so onlyNotifyIfNoReviews would always notify the user, regardless of there being reviews "today" or not.

I've added new tests to monitor this blindspot and I'm fairly sure it's all now working according to plan. Now unblocked and ready for review!

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from f2d2cce to 2f9d231 Compare December 26, 2025 18:52
@ericli3690
Copy link
Member Author

  • Made the card trigger thresholds of various dummy cards more explicit in NotificationServiceTest
  • Eliminated some redundant col.decks.select calls in NotificationServiceTest using setAsSelected = true

@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge and removed Needs Review labels Dec 28, 2025
@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge and removed Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) labels Dec 28, 2025
Copy link
Contributor

@criticalAY criticalAY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thankyou!!

Comment on lines 82 to 110
private fun createAndSaveDummyDeckSpecificReminder(did: DeckId): ReviewReminder {
val reviewReminder =
ReviewReminder.createReviewReminder(
cardTriggerThreshold = ReviewReminderCardTriggerThreshold(1),
scope = ReviewReminderScope.DeckSpecific(did),
)
ReviewRemindersDatabase.editRemindersForDeck(did) { mapOf(ReviewReminderId(0) to reviewReminder) }
return reviewReminder
}

private fun createAndSaveDummyAppWideReminder(): ReviewReminder {
val reviewReminder =
ReviewReminder.createReviewReminder(
cardTriggerThreshold = ReviewReminderCardTriggerThreshold(1),
)
ReviewRemindersDatabase.editAllAppWideReminders { mapOf(ReviewReminderId(1) to reviewReminder) }
return reviewReminder
}

private fun triggerDummyReminderNotification(reviewReminder: ReviewReminder) {
val intent =
NotificationService.getIntent(
context,
reviewReminder,
NotificationService.NotificationServiceAction.ScheduleRecurringNotifications,
)
NotificationService().onReceive(context, intent)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do with better helper methods to accept optional parameters so you can reuse them everywhere but not gonna block it over it, Looks good to me apart from the nitpicks David suggested

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I get what you mean, but respectfully I'm probably going to leave it as is. Based on my understanding of this test file, making a bunch of helper functions would be really complicated and likely not worth the effort. Thanks for the speedy review!!

@criticalAY criticalAY added Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) and removed Needs Second Approval Has one approval, one more approval to merge Needs reviewer reply Waiting for a reply from another reviewer labels Dec 28, 2025
@ericli3690 ericli3690 removed the Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) label Dec 28, 2025
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 2f9d231 to c765099 Compare December 28, 2025 20:44
@ericli3690
Copy link
Member Author

Thanks to the both of you for the rapid reviews and kind words! I'm going to yank this guy off the merge queue to get these last few nits handled, though, as making a separate PR to patch those will give me a headache. Now just waiting for David's clarification on a few comments that I unfortunately don't understand entirely. No rush!

@ericli3690 ericli3690 added Needs Second Approval Has one approval, one more approval to merge Needs reviewer reply Waiting for a reply from another reviewer and removed Needs Second Approval Has one approval, one more approval to merge labels Dec 28, 2025
fun createReviewReminder(
time: ReviewReminderTime,
cardTriggerThreshold: ReviewReminderCardTriggerThreshold,
time: ReviewReminderTime = ReviewReminderTime(0, 0),
Copy link
Member

@david-allison david-allison Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the default midnight? Most people would be sleeping, it's also not close to the default rollover time

Could you add a comment with justification (or remove it if you want callers to always be explicit with the time)

Copy link
Member Author

@ericli3690 ericli3690 Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, ok, after thinking this through some more, you're right - ideally callers should be explicit with the time, so I should remove this default argument. I initially added the default argument to streamline creating review reminders in the test files, but I can accomplish that by adding a small helper function that auto-fills the time instead. While I'm at it, I can make setting the threshold and scope etc. easier via the helper, too:

    private fun createTestReminder(
        deckId: DeckId? = null,
        thresholdInt: Int = 1,
        onlyNotifyIfNoReviews: Boolean = false,
    ) = ReviewReminder.createReviewReminder(
        time = ReviewReminderTime(hour = 12, minute = 0),
        cardTriggerThreshold = ReviewReminderCardTriggerThreshold(thresholdInt),
        scope = if (deckId != null) ReviewReminderScope.DeckSpecific(deckId) else ReviewReminderScope.Global,
        onlyNotifyIfNoReviews = onlyNotifyIfNoReviews,
    )

I initially shied away from this approach because it felt a bit ugly, but after writing it out it actually looks pretty good. Implemented!

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from c765099 to 7c6553a Compare January 7, 2026 05:47
GSoC 2025: Review Reminders

Adds an advanced review reminder option. When this setting is enabled, the review reminder only triggers notifications if the deck the review reminder is for has not been reviewed yet today.

Adds a checkbox to the AddEditReminderDialog to toggle this setting on or off. Adds a method to NotificationService to check if a deck (or all decks) have been reviewed yet today. The check is accomplished via a database query of the `revlog` and `cards` tables. In my experience there is no noticeable latency, the query I've written should be fairly efficient.

Adds a boolean field to store the state of this setting to ReviewReminder. Adds unit tests. Adds default arguments to parameters in ReviewReminder to make AlarmManagerServiceTest and NotificationServiceTest less verbose.
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-only-notify-if-no-reviews branch from 7c6553a to bae06e0 Compare January 7, 2026 05:51
@ericli3690
Copy link
Member Author

Addressed David's feedback! David, if everything looks good to you, feel free to chuck this guy in the merge queue, I'm happy with it now. Cheers!

@david-allison david-allison removed the Needs reviewer reply Waiting for a reply from another reviewer label Jan 7, 2026
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cheers!!!! Looks great

@david-allison david-allison added this pull request to the merge queue Jan 7, 2026
@david-allison david-allison added the Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) label Jan 7, 2026
Merged via the queue into ankidroid:main with commit 5b630b8 Jan 7, 2026
15 checks passed
@github-actions github-actions bot added this to the 2.24 release milestone Jan 7, 2026
@github-actions github-actions bot removed the Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) label Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

GSoC Pull requests authored by a Google Summer of Code participant [Candidate/Selected], for GSoC mentors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants