Skip to content

feat(reminders): AddEditReminderDialog#19109

Merged
david-allison merged 7 commits intoankidroid:mainfrom
ericli3690:ericli3690-review-reminders-add-edit-reminders-dialog-august
Oct 2, 2025
Merged

feat(reminders): AddEditReminderDialog#19109
david-allison merged 7 commits intoankidroid:mainfrom
ericli3690:ericli3690-review-reminders-add-edit-reminders-dialog-august

Conversation

@ericli3690
Copy link
Member

@ericli3690 ericli3690 commented Aug 19, 2025

Purpose / Description

Allows users to add and edit review reminders.

All commit messages, together:

  • initializeScheduleRemindersDeckSpinner doesn't properly handle what happens if showAllDecks is true. This makes pressing on a deck select the wrong deck. This commit fixes this.
  • Added TimePicker styling, used when users edit the time of a review reminder.
  • The AddEditReminderDialog displays the current time in the TimePicker when a review reminder is being created. Adds a utility function to ReviewReminderTime's companion object to calculate this time. I decided to put this function within ReviewReminderTime so it can be accessed by both AddEditReminderDialog and AddEditReminderDialogViewModel.
  • XML file for AddEditReminderDialog. Allows time and deck modification. Also has a dropdown for advanced settings, including minimum card trigger threshold (with more advanced settings hopefully coming soon). The advanced settings will be grouped under a dropdown to prevent them from overwhelming users when the dialog initially opens.
  • Created a ViewModel for the AddEditReminderDialog. A specific view model is created because this allows the dialog's state to persist across redraws, ex. if the theme changes, if the device rotates, etc. It also centralizes the data of the review reminder being edited in a single source of truth.
  • Creates AddEditReminderDialog.
  • Adds DialogMode, which represents whether the dialog is in adding or editing mode.
  • Sets submit, cancel, and delete actions for the dialog; this is the main way users can delete review reminders. Deletion is locked behind a confirmation box just in case the user accidentally clicks the button.
  • Results for the deck picker dropdown in the dialog are received in ScheduleReminders and sent to the AddEditReminderDialog via a FragmentResult. See the docstring of onDeckSelected in ScheduleReminders to see why this is done.
  • Filling in the values in the dialog fields and setting listeners for when the values change is pulled out into separate methods for readability.
  • Adds showTimePickerDialog. Shows a TimePicker dialog for picking review reminder time via modern Material3 guidelines. Overrides onConfigurationChanged to properly handle device rotation, which for some reason is not handled by default by MaterialTimePicker.
  • An edited review reminder is passed back to ReviewRemindersDatabase as a FragmentResult, which is a simple way of passing information between fragments.
  • Adds a fragment result listener to ScheduleReminders to detect when an AddEditReminderDialog has completed, and if so, edits the database and UI accordingly.
  • Filled out the addReminder and editReminder methods to show the AddEditReminderDialog.

UI Screenshots:
Dialog:
image
image

Error handling:
image

Sub-dialogs:
image

TimePickers:
image
image

Tablet:
image
image

Fixes

For GSoC 2025: Review Reminders

Approach

Creates a ViewModel to store the state of the dialog. Information is passed between the dialog and ScheduleReminders via fragment results, which is a simple and straightforward API.

How Has This Been Tested?

  • Works on a physical Samsung S23, API 34; can create, read, update, and delete review reminders. I've been casually testing this code for the last month as I've developed other features, and I think it should be fairly stable.
  • Works on a Medium Tablet, API 36.
  • I plan on writing instrumented tests in the last week of August.

Learning (optional, can help others)

  • This code has been waiting to be merged for a while! Really happy to see it finally up as a PR.
  • Had a lot of trouble in the final stretch with TimePicker. Turns out there are three different ways to make it: using XML, using the provided TimePickerDialog, and using MaterialTimePicker. The latter is the only way to get the nice styling. Also, the background was always tinted a bit blue no matter what I did. Turns out I had to add <item name="elevationOverlayEnabled">false</item>.
  • Also had trouble making sure the TimePicker dialog redrew whenever you rotated your screen. I ended up redrawing it manually since there didn't seem to be built-in handling for it.

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 requested a review from Copilot August 19, 2025 05:10
@ericli3690 ericli3690 added Needs Review GSoC Pull requests authored by a Google Summer of Code participant [Candidate/Selected], for GSoC mentors labels Aug 19, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a comprehensive add/edit dialog for review reminders, allowing users to create, modify, and delete review reminders with time and deck selection functionality. The implementation includes a ViewModel for state management, advanced settings with card trigger thresholds, and proper integration with the existing reminder system.

Key changes:

  • Added AddEditReminderDialog with full CRUD functionality for review reminders
  • Implemented AddEditReminderDialogViewModel for persistent state management across configuration changes
  • Enhanced ScheduleReminders with RecyclerView adapter and database integration
  • Fixed deck spinner selection logic to properly handle "All Decks" option

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
styles.xml Added TimePickerStyle theme for Material3 time picker styling
add_edit_reminder_dialog.xml Complete dialog layout with time selection, deck dropdown, and expandable advanced settings
ScheduleRemindersAdapter.kt New RecyclerView adapter for displaying reminder list items
ScheduleReminders.kt Enhanced with database operations, UI state management, and dialog result handling
AddEditReminderDialogViewModel.kt ViewModel for managing dialog state and field values
AddEditReminderDialog.kt Main dialog implementation with time picker, validation, and fragment result communication
DeckSpinnerSelection.kt Fixed deck selection logic to properly show "All Decks" option
Comments suppressed due to low confidence (1)

AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/AddEditReminderDialog.kt:65

  • The parameter type is inconsistent with the LiveData type. The function accepts Int? but the LiveData is typed as Int. This could lead to null pointer exceptions when setting null values.
     * In particular, whether this dialog will be used to add a new review reminder or edit an existing one.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch from 6fc8ff3 to c165680 Compare August 19, 2025 05:33
@ericli3690
Copy link
Member Author

Resolved Copilot feedback. Implemented a Google Accessibility Scanner recommendation.

@ericli3690
Copy link
Member Author

Once #18964 is merged, I'll rebase this PR on top of main. Until then, this PR is ready for review!

@criticalAY criticalAY added the Blocked by dependency Currently blocked by some other dependent / related change label Aug 19, 2025
Comment on lines +258 to +263
<style name="TimePickerStyle" parent="ThemeOverlay.Material3.MaterialTimePicker">
<item name="elevationOverlayEnabled">false</item>
<item name="colorTertiaryContainer">?colorPrimary</item>
<item name="colorOnTertiaryContainer">?colorOnPrimary</item>
<item name="colorPrimaryContainer">?colorPrimary</item>
</style>
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏼

android:orientation="horizontal">

<!-- The following image's icon switches between ic_baseline_chevron_right_24 and ic_expand_more_black_24dp_xml -->

Copy link
Contributor

Choose a reason for hiding this comment

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

Can remove this line? or maybe comment too or is it needed?

Copy link
Member

Choose a reason for hiding this comment

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

I don't understand the part of your request about the comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

@criticalAY Like Arthur, I'm also a little confused. I think what you mean is that I should remove this comment? I've gone ahead and done that. Let me know if you wanted something else instead.

* the input type the user is using to enter the threshold into the app. In other words, this class reflects the concrete
* EditText fields in the dialog, not abstract backend data representations.
*/
class AddEditReminderDialogViewModel(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

}

// 2. state -> viewModel: Configure how the UI reacts to changes in the state
viewModel.time.observe(this) { time ->
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we not using viewLifecycleOwner?

Copy link
Contributor

Choose a reason for hiding this comment

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

viewModel.time.observe(viewLifecycleOwner) { time -> … }
viewModel.advancedSettingsOpen.observe(viewLifecycleOwner) { advancedSettingsOpen -> … }

Can we do better here, inspect?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, I thought about using viewLifecycleOwner, but after experimenting with it, I've determined that we shouldn't use it because this is a DialogFragment. According to the docs:

Note: When subscribing to lifecycle-aware components such as LiveData, never use viewLifecycleOwner as the LifecycleOwner in a DialogFragment that uses Dialog objects. Instead, use the DialogFragment itself, or, if you're using Jetpack Navigation, use the NavBackStackEntry.

Source: https://developer.android.com/guide/fragments/dialogs

}

// Fill in initial field values
timeButton.text = viewModel.time.value.toString()
Copy link
Contributor

Choose a reason for hiding this comment

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

rely only on observers?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, that line is unnecessary. Deleted.

return dialog
}

fun setUpDialogFields() {
Copy link
Contributor

Choose a reason for hiding this comment

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

split this method into smaller ones

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!

Comment on lines 123 to 135
setFragmentResultListener(ADD_EDIT_DIALOG_RESULT_REQUEST_KEY) { _, bundle ->
val modeOfFinishedDialog =
BundleCompat.getParcelable(
requireArguments(),
ACTIVE_DIALOG_MODE_ARGUMENTS_KEY,
AddEditReminderDialog.DialogMode::class.java,
)
val newOrModifiedReminder = BundleCompat.getParcelable(bundle, ADD_EDIT_DIALOG_RESULT_REQUEST_KEY, ReviewReminder::class.java)

Timber.d("Dialog result received with recent dialog mode: %s", modeOfFinishedDialog)
if (modeOfFinishedDialog == null) return@setFragmentResultListener
handleAddEditDialogResult(newOrModifiedReminder, modeOfFinishedDialog)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Too dense extract it out

Copy link
Member Author

Choose a reason for hiding this comment

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

I've cleaned up the code here a bit but I don't think it can be extracted in any meaningful way. Here's what it looks like now:

        setFragmentResultListener(ADD_EDIT_DIALOG_RESULT_REQUEST_KEY) { _, bundle ->
            val modeOfFinishedDialog =
                BundleCompat.getParcelable(
                    requireArguments(),
                    ACTIVE_DIALOG_MODE_ARGUMENTS_KEY,
                    AddEditReminderDialog.DialogMode::class.java,
                ) ?: return@setFragmentResultListener
            val newOrModifiedReminder =
                BundleCompat.getParcelable(
                    bundle,
                    ADD_EDIT_DIALOG_RESULT_REQUEST_KEY,
                    ReviewReminder::class.java,
                )
            Timber.d("Dialog result received with recent dialog mode: %s", modeOfFinishedDialog)
            handleAddEditDialogResult(newOrModifiedReminder, modeOfFinishedDialog)
        }

Is there a specific way you were thinking of extracting out some of this code?

/**
* When a [AddEditReminderDialog] instance finishes, we handle the result of the dialog fragment via this method.
*/
private fun handleAddEditDialogResult(
Copy link
Contributor

Choose a reason for hiding this comment

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

too much in one method, attaching a patch

}
triggerUIUpdate()

showSnackbar(
Copy link
Contributor

Choose a reason for hiding this comment

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

Use an anchor to avoid this
Screenshot 2025-08-20 at 3 56 47 AM

@criticalAY criticalAY added Needs Author Reply Waiting for a reply from the original author and removed Needs Review labels Aug 19, 2025
Copy link
Member

@Arthur-Milchior Arthur-Milchior left a comment

Choose a reason for hiding this comment

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

I tried to review, but very little to state on top of what @criticalAY mentioned

const val DECK_SELECTION_RESULT_REQUEST_KEY = "reminder_deck_selection_result_request_key"

private const val SERIALIZATION_ERROR_MESSAGE =
"Something went wrong. A serialization error was encountered while working with review reminders."
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a hard coded string?

If it's because you're not sure it'll be in the final product and don't want it translated, add a TODO please.
If it's fine to translate it, put it with resources please

Copy link
Member Author

Choose a reason for hiding this comment

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

It's hard-coded because I'm not sure if its wording is in its final form, i.e. I don't want it translated yet. Thus, I've added a TODO comment. Thanks!

android:orientation="horizontal">

<!-- The following image's icon switches between ic_baseline_chevron_right_24 and ic_expand_more_black_24dp_xml -->

Copy link
Member

Choose a reason for hiding this comment

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

I don't understand the part of your request about the comment.

@Arthur-Milchior Arthur-Milchior removed the Blocked by dependency Currently blocked by some other dependent / related change label Aug 24, 2025
@ericli3690 ericli3690 marked this pull request as draft August 26, 2025 02:40
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch 2 times, most recently from 56ae2d0 to 3bc07f5 Compare August 27, 2025 01:49
@ericli3690
Copy link
Member Author

  • Removed comment in XML file
  • Removed unnecessary text setter
  • Cleaned up setFragmentResultListener in ScheduleReminders
  • Broke up handleAddEditDialogResult and setUpDialogFields into smaller methods
  • Added an anchor to the feedback snackbar to make it show up above the TAB
  • Added TODO comments to the hardcoded deserialization error strings

Ready for review again!

@ericli3690 ericli3690 marked this pull request as ready for review August 27, 2025 02:07
@ericli3690 ericli3690 added Needs Review and removed Needs Author Reply Waiting for a reply from the original author Has Conflicts labels Aug 27, 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 to me!

@criticalAY criticalAY added Needs Second Approval Has one approval, one more approval to merge and removed Needs Review labels Aug 27, 2025
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch from 3bc07f5 to 16bc8f6 Compare August 28, 2025 07:02
@ericli3690
Copy link
Member Author

Minor edits:

  • Non-functional change moving view retrieval from the onCreateDialog method to the individual sub-methods for better readability.
  • Added more information to a ViewModel logger.

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.

Don't let this review be blocking, I'm happy, and I'm aware this is blocking other reviews. If you want this merged, let me know.

In general, more logic could be in the VIewModel

I mentioned the review threshold should be split over ~3 lines, could you test this (if you understand my intent), as I think:

  • it will look nicer
  • it allows you to use .error and simplify onSubmit

viewModelFactory {
initializer {
Timber.d("Initializing AddEditReminderDialogViewModel")
when (val mode = dialogMode) {
Copy link
Member

Choose a reason for hiding this comment

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

I believe you can use SavedStateHandle to access arguments inside a ViewModel

Comment on lines 196 to 197
is DialogMode.Add -> "Add review reminder"
is DialogMode.Edit -> "Edit review reminder"
Copy link
Member

Choose a reason for hiding this comment

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

Let's lock them into R.string

private fun setUpTimeButton() {
val timeButton = contentView.findViewById<MaterialButton>(R.id.add_edit_reminder_time_button)
timeButton.setOnClickListener {
Timber.d("Time button clicked")
Copy link
Member

Choose a reason for hiding this comment

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

Prefer Timber.i for user interactions:

if there's a bug, I want a brief understand of what a user pressed (no sensitive information in these)

return
}

val reminderToBeReturned =
Copy link
Member

Choose a reason for hiding this comment

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

nit: add a TODO to move this to the ViewModel

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented!

Timber.d("Submitted dialog")

// Validate numerical fields
if ((viewModel.cardTriggerThreshold.value ?: -1) < 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you display validation instead (see TextInputLayout.error)?

Once you've done that, you can disable 'submit', and turn calling 'onSubmit' when it's in a bad state into an error state. (R.string.something_wrong)

/**
* TODO: Move to string resources for translation once review reminders are stable.
*/
private const val SERIALIZATION_ERROR_MESSAGE =
Copy link
Member

Choose a reason for hiding this comment

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

The error itself isn't actionable, so a user doesn't need a translated string

I'd go with something like:

R.string.something_wrong_error_code: Something went wrong (%s), where %s: SERIALIZATION_ERROR

Feel free to pull this into another PR and it can quickly be merged

Copy link
Member Author

Choose a reason for hiding this comment

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

As mentioned in Discord, I'm thinking of changing how we handle a catastrophic review reminders database read. Instead of just showing an error message, I'd like to delete the corrupted review reminders so the app can still be used. Yes, this will cause data loss, but I don't think review reminders are so important that they should block normal interaction with the app. I'll likely create a string along the lines of "Some of your review reminders were corrupted and were deleted, sorry". I'll add an ACRA log, too.

For now, I've deleted these TODO strings and will clean them up in a forthcoming PR.

@ericli3690
Copy link
Member Author

Thanks for the review! I'll take a look at implementing your suggested changes shortly.

@ericli3690 ericli3690 marked this pull request as draft September 6, 2025 16:41
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch from e70066e to 330c8b6 Compare September 8, 2025 22:31
@ericli3690 ericli3690 marked this pull request as ready for review September 8, 2025 22:48
@ericli3690
Copy link
Member Author

  • Moved the logic for creating the initial state of the AddEditReminderDialog to the ViewModel.
  • Moved the logic for turning the state of the ViewModel into a ReviewReminder to the ViewModel.
  • Moved the helper method for getting the current time as a ReviewReminderTime to ReviewReminderTime's companion object so that it can easily be called from both the Dialog and the ViewModel.
  • Moved "Add review reminder" and "Edit review reminder" strings to 10-preferences so they can be translated, as they are likely set in stone.
  • Used Android TextInputLayout for showing an error if the card trigger threshold is inputted incorrectly.
  • Added a null check to adding an onClickListener to the "delete" negativeButton -- technically this is not needed, but it's good to communicate that the delete button is not always present (i.e. if the user is adding a new card, which cannot be deleted as it does not exist yet).
  • Switched user interaction loggers from .d to .i as suggested by David.

Images in the PR description have been updated! Ready for review.

@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch from 330c8b6 to 2c4b023 Compare September 22, 2025 15:54
@ericli3690
Copy link
Member Author

  • Rebased.

GSoC 2025: Review Reminders

- `initializeScheduleRemindersDeckSpinner` doesn't properly handle what happens if showAllDecks is true. This makes pressing on a deck select the wrong deck. This commit fixes this.
GSoC 2025: Review Reminders

- Added TimePicker styling, used when users edit the time of a review reminder.
GSoC 2025: Review Reminders

- The AddEditReminderDialog displays the current time in the TimePicker when a review reminder is being created. This change adds a utility function to ReviewReminderTime's companion object to calculate this time. I decided to put this function within ReviewReminderTime so it can be accessed by both AddEditReminderDialog and AddEditReminderDialogViewModel.
GSoC 2025: Review Reminders

- XML file for AddEditReminderDialog. Allows time and deck modification. Also has a dropdown for advanced settings, including minimum card trigger threshold (with more advanced settings hopefully coming soon). The advanced settings will be grouped under a dropdown to prevent them from overwhelming users when the dialog initially opens.
GSoC 2025: Review Reminders

- Creates AddEditReminderDialog.
- Adds DialogMode, which represents whether the dialog is in adding or editing mode.
- Created a ViewModel for the AddEditReminderDialog. A specific view model is created because this allows the dialog's state to persist across redraws, ex. if the theme changes, if the device rotates, etc. It also centralizes the data of the review reminder being edited in a single source of truth.
- Sets submit, cancel, and delete actions for the dialog; this is the main way users can delete review reminders. Deletion is locked behind a confirmation box just in case the user accidentally clicks the button.
- Results for the deck picker dropdown in the dialog are received in ScheduleReminders and sent to the AddEditReminderDialog via a FragmentResult. See the docstring of `onDeckSelected` in ScheduleReminders to see why this is done.
- Filling in the values in the dialog fields and setting listeners for when the values change is pulled out into a separate methods for readability.
- Adds `showTimePickerDialog`. Shows a TimePicker dialog for picking review reminder time via modern Material3 guidelines. Overrides `onConfigurationChanged` to properly handle device rotation, which for some reason is not handled by default by MaterialTimePicker.
- An edited review reminder is passed back to ReviewRemindersDatabase as a FragmentResult, which is a simple way of passing information between fragments.
GSoC 2025: Review Reminders

- Adds a fragment result listener to ScheduleReminders to detect when an AddEditReminderDialog has completed, and if so, edits the database and UI accordingly.
- Filled out the `addReminder` and `editReminder` methods to show the AddEditReminderDialog.
GSoC 2025: Review Reminders

- Realized a lint annotation in ScheduleRemindersAdapter is unnecessary.
@ericli3690 ericli3690 force-pushed the ericli3690-review-reminders-add-edit-reminders-dialog-august branch from 2c4b023 to c049c11 Compare September 26, 2025 02:18
@ericli3690
Copy link
Member Author

@ericli3690
Copy link
Member Author

@david-allison Thanks for the fast merge on the 24h-time-formatting PR I created! Do you think we could quickly get this guy in, too? I'll be able to put more code up for review once it's in, and I think I've addressed all your feedback from the last time you looked at it. I've heard you're on vacation, though; if that's the case, please ignore this!

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.

LGTM, cheers!

Sorry this sat!

@david-allison david-allison added this pull request to the merge queue Oct 2, 2025
Merged via the queue into ankidroid:main with commit 42d5c0c Oct 2, 2025
10 checks passed
@github-actions github-actions bot added this to the 2.23 release milestone Oct 2, 2025
@github-actions github-actions bot removed the Needs Second Approval Has one approval, one more approval to merge label Oct 2, 2025
@ericli3690
Copy link
Member Author

No problem at all, thanks!!

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.

4 participants

Comments