Skip to content

Merge upstream AnkiDroid main (2026-10-30) - #73

Merged
float3 merged 29 commits into
ankiquestfrom
merge-upstream-2026-10-30
Oct 30, 2026
Merged

float3 merged 29 commits into
ankiquestfrom
merge-upstream-2026-10-30

Conversation

@float3

@float3 float3 commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Merges the 27 commits on upstream AnkiDroid main since #69.

Merged cleanly. Upstream bumped the backend to 0.1.75 and the version to 2.26.0alpha10, refactored the WebView, routed activity storage through the active profile, and moved the progress dialog and touch blocking into :common:android. AnkiDroid/build.gradle.kts, AnkiDroidApp.kt and libs.versions.toml changed on both sides, in different places.

ktlint, assemblePlayDebug and the AnkiQuest unit tests pass in the Nix dev shell.

GuTS805 and others added 28 commits October 29, 2026 15:37
…yIfNoReviews

`wasScopeReviewedToday` only matched `cards.did` against the reminder's
deck, so reviews of cards in a subdeck, or of cards temporarily moved into
a filtered deck (home deck in `odid`), were ignored and the reminder still
fired. The due count in the notification already includes subdecks.

Check the deck and its children against both `did` and `odid`.

Fixes 22848

Assisted-by: Claude Opus 5.5
Backend 0.1.75 moved the Android adapters, native libraries, and web
assets into `rsdroid-android`.

See https://redirect.github.com/ankidroid/Anki-Android-Backend/pull/767

Assisted-by: GPT-6-Astra max
The publish job sometimes ran out of heap while packaging a universal
APK, after Play already had the build, so no GitHub release was made.
release.sh restarts Gradle for every build, so raise the heap from 3 GB
to 4 GB in the Gradle user home, where each new daemon picks it up.

Fixes 22855

Assisted-by: Claude Opus 5.5
the class should be as generic as possible. Its implementers should be the ones handling their own listeners

Currently, CongratsPage deals with all of that, so the listener isn't necessary

Assisted-by: Claude Opus 5.5
Importing from the device storage failed on phones where the system
External Storage app has lost "All files access", and each failure was
sent as a crash report. We now read the picked file through MediaStore,
which accepts the access we already have.

Fixes 22822
Assisted-by: Claude Opus 5.5
Rebuild, Empty and Unbury acted on the collection's selected deck,
while the panel's other actions use the deck it shows. If the panel
was out of date, for example after a failed refresh, they could act
on a deck that wasn't shown. They now act on the shown deck.

Assisted-by: Claude Opus 5.5
Only withProgressDialog reads and writes it, so it no longer needs to
live on AnkiDroidApp.

Assisted-by: Claude Opus 5.5
withProgressDialog and the progress observer both use it, and it only
needs a Window. The package is unchanged, so callers are too.

Assisted-by: Claude Opus 5.5
withProgressDialog is its only user, and the attributes and dimension
it reads already live there. CustomButtonBarButtonStyle moves with it,
since only this style uses it.

Assisted-by: Claude Opus 5.5
Widgets checked storage access on the main thread before every update.
That check can wait on Android's storage service, for example at boot
while an SD card is being mounted, which caused an ANR.

Fixes 22825

Assisted-by: Claude Opus 5.5
ProfileManager.wrap gives a context the active profile's prefs and
folders without creating the directories again. Default contexts come
back unchanged.

Assisted-by: Claude Opus 5.5
Activities were still reading and writing the Default profile's prefs
and files. AnkiActivity and the intent handlers now wrap their base
context for the active profile.

Assisted-by: Claude Opus 5.5
Each public method of SafeWebViewLayout had its own
`if (warnIfNotActive("name")) return` line before using `webView`.
The guard was easy to forget, the method-name strings could drift on
rename, and each return type needed its own default.

`activeWebView` returns the inner WebView only while it is active, so
the check and the access are one expression and defaults come from
`?:`. Behavior is unchanged, except that the warning no longer names
the method.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
zoomBy was added after the destroyed-state guard and called the inner
WebView directly, so ReviewerFragment.resetZoom() could reach a
destroyed WebView, which the WebView.destroy() contract forbids.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
The raw reference may point at a destroyed WebView. Naming it
`uncheckedWebView` makes direct use a visible choice, so new methods
reach for `activeWebView` instead.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Both callers of recreateInnerWebView reach it only after
onRenderProcessGone removed the terminated WebView from the layout, so
the `parent == this` check never matched.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Extract findFragmentOrNull, which tryRecoverDestroyedWebViewIfNeeded
also needs, and handle fragment and activity hosts with one
implements-the-listener check instead of two copies.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Every implementation ignored the parameter and reconfigured through
SafeWebViewLayout, which guards calls on a destroyed WebView. Dropping
it removes the only way the raw WebView left the layout.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
CardViewerFragment, used by the reviewer and the previewer, was the only
host calling destroy(), which only calls WebView.destroy(). Use
safeDestroy() like every other host, so the WebView also stops loading,
drops its clients and is removed from the layout.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
destroy() no longer has callers outside tests and repeated
safeDestroy()'s destroyed-state handling. Remove it, and rename
safeDestroy() to destroy(), like WebView.destroy().

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
SafeWebViewLayout always passed a callback, so the WebView serialized
and returned every script result even when the caller ignored it.
Chromium skips that work when the callback is null.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
settings, scale and setAcceptThirdPartyCookies call into the WebView,
which must be used on the main thread, like the other methods.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
The property kept its own copy of the style, which nothing read and
which drifted from the WebView after a recreation.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Espresso could pick the activity window while a reminder dialog was
still taking focus, then wait 10 s (RootViewWithoutFocusException), and
a slow tap on the picker's mode button only showed its tooltip. Look in
the dialog window and set the time on the picker directly.

Assisted-by: Claude Opus 5.5
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Snapshot diff report vs ankiquest. Open screenshot-diff for diffs.

  • AnkiquestHomeScreenshotTest: 1 change
All 1 changed screenshots

AnkiquestHomeScreenshotTest

  • today_320dp_compare.png

@float3
float3 merged commit 9351947 into ankiquest Oct 30, 2026
23 checks passed
@float3
float3 deleted the merge-upstream-2026-10-30 branch October 30, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants