Skip to content

Merge upstream AnkiDroid (2026-10-28) - #67

Open
float3 wants to merge 54 commits into
ankiquestfrom
merge-upstream-2026-10-28
Open

float3 wants to merge 54 commits into
ankiquestfrom
merge-upstream-2026-10-28

Conversation

@float3

@float3 float3 commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Merges the 50 upstream commits from ankidroid/main. Only conflict was an import line in AnkiDroidApp.kt (kept both). Supersedes #64.

criticalAY and others added 30 commits October 27, 2026 17:49
Logging in or signing up from the shared decks screen left people on
AnkiWeb's own deck list, and Back could not get past its login page.
Once AnkiWeb lands on its after-login page, reload the last shared
decks page and drop the login pages from history. Back still goes
through the shared pages opened before logging in.

Assisted-by: Claude Opus 5.5
The home button clears the history once the page has loaded, after
Back was already worked out, so it relied on AnkiWeb reporting the
page again. Update Back right after the clear. The tests now finish
each page load and keep the current page on a clear, as a real
WebView does.

Assisted-by: Claude Opus 5.5
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Observing from a fragment used the activity's lifecycle, so the
observer outlived the fragment, kept its ViewModel alive and could
leave its loading dialog behind. It now follows the fragment: a
stopped fragment gives back the touches its op blocked, the dialog
goes away with the fragment, and it never shows or dismisses the
dialog in the middle of a fragment transaction.

Assisted-by: Claude Opus 5.5
Screens with the same ViewModel class share one loading dialog, and
each closed it without knowing about the others. An idle screen
closed another screen's dialog when it was added, restarted or
destroyed, and whichever operation finished first closed it while
another was still running. A screen kept stopped after the app came
back from the background also left the dialog up once its operation
ended.

Now each screen holds the dialog while it needs it. It closes when
the last screen lets go, the screen still using it shows its own
message and cancel action again, and a screen that hasn't started
lets go when the activity starts.

Assisted-by: Claude Opus 5.5
Rebuilding and emptying a filtered deck now report progress from
StudyOptionsViewModel. The fragment observes it in onCreate, since
coming back from another panel on a tablet recreates its view.

Assisted-by: Claude Opus 5.5
Local unit test runs could stop with "Gradle Test Executor N finished
with non-zero exit value 10". LeakCanary only runs in local debug
builds, kept an earlier test's app, and crashed on it in a later test,
so ACRA ended the worker. CI never runs it, so skip it here too.

Assisted-by: Claude Opus 5.5
Assisted-by: GPT-6.1-Sol xhigh
Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
and `DialogPreviews`

Part of 22694

Assisted-by: GPT-6.1-Sol xhigh
Fixes 22694

Assisted-by: GPT-6.1-Sol xhigh
ShadowNullExternalFilesDir implemented ContextImpl without extending
ShadowContextImpl, so a later getSharedPreferences cast failed when
that shadow was still attached to a live ContextImpl. Drive the
existing ShadowContextImpl null branch via ShadowEnvironment instead.

The unwritable-collection fixture grants legacy storage permission
before startup so a saved collection path still reaches the storage
fatal error once preferences keep working.
Part of 19572

Assisted-by: GPT-6
Assisted-by: GPT-6-Astra xhigh
Assisted-by: GPT-6.1-Sol xhigh
Prep to identify and clear stale files.

Create the directory only when writing a snapshot, so browser startup does
not require writable snapshot storage.

Recreate it before each write so cached paths remain usable if the cache
is cleared.

Part of 19572

Assisted-by: GPT-6
Assisted-by: GPT-6-Astra xhigh
Assisted-by: GPT-6.1-Sol xhigh
One cache cleanup should not be skipped because another fails.

Assisted-by: GPT-6.1-Sol xhigh
Snapshots older than 7 days are deleted.

This only affects the current profile.

Part of 19572

Assisted-By: Claude Fable 5
Assisted-by: GPT-6
Assisted-by: GPT-6-Astra xhigh
Assisted-by: GPT-6.1-Sol xhigh
We now handle this case as well as possible.

Fixes 19572

Assisted-by: GPT-6.1-Sol xhigh
Follow up to 19572

Assisted-by: GPT-6.1-Sol xhigh
The request body buffer was sized straight from the header, so a
missing or malformed length threw a RuntimeException, which dropped
the connection without a status.

Reject missing (411) and invalid (400) lengths. Only ASCII digits are
accepted, as RFC 9110 requires: `toIntOrNull()` alone allows a sign
and non-ASCII digits. Lengths beyond Int are invalid too, as `toInt()`
would truncate them. NanoHTTPD sends the status and closes the
connection.

Assisted-by: Claude Opus 5.5
A handler exception with a null message produced
`buildResponse(null, INTERNAL_ERROR)`, whose null branch ignored the
status: NanoHTTPD's `newFixedLengthResponse(null)` is 200 OK with an
empty body. Anki's `postProto` then decoded a default protobuf.

Use the exception class name when there is no message. The message
stays the body, as Anki's JS matches '500: Interrupted'.

Assisted-by: Claude Opus 5.5
Methods other than POST and GET were answered 200 OK with their body
left unread. On a kept-alive connection, NanoHTTPD would then parse
that body as the next request.

Respond 405 with `Allow: POST`, as RFC 9110 requires, and close the
connection.

Assisted-by: Claude Opus 5.5
The response data is already in memory, so chunked encoding gains
nothing: send it with a Content-Length instead.

Assisted-by: Claude Opus 5.5
david-allison and others added 21 commits October 28, 2026 15:17
Affects all surface containers.

Some of our XML colors are heavily transparent:

By using `compositeOver` this blends it with the opaque surface color
of the theme, producing a tinted window as expected, rather than
a semi-transparent popup menu.

Part of 10397

Assisted-by: GPT-6-Astra xhigh
Assisted-by: GPT-6.1-Sol xhigh
Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Callers can set the initial cursor/range (default: end of text).

Save text and selection together.
Selection changes do not mark the field as edited.

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
e.g. `Science Biology` => `Science::Biology`

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Text displayed when no validation error is displayed.

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Text below the title

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Validation can be deferred until confirmation.

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
For both the buttons + keyboard input

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
If vertical space is limited, the buttons should remain available.

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Make the animations opacity-only.

Remove the motion from `AnkiDroidDialog` so the dialog does not
'jump' on dialogs which trigger focus/soft keyboard opening.

Part of 10397

Assisted-by: GPT-6-Astra xhigh
Assisted-by: GPT-6.1-Sol xhigh
For renaming:

```
parent::child -> `PARENT::child`
```

the leaf name remained 'child'. `TagName` treats the paths as equal
so the change could be missed.

The exact text is now compared, ensuring a future Compose screen
would handle the update.

Part of 10397

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
In:
`a::b::c`, searching for `b` no longer expands `a::b::c`
But if `a` was collapsed, it would be expanded

Part of 10397

Assisted-by: GPT-6-Astra xhigh
* flip `collapsed` and update the visible rows before backend access
* This means taps may be queued, use `previousSave` to enqueue backend updates
* All other operations wait for the save

The UI is not reverted on failure

Part of 10397

Assisted-by: GPT-6-Astra xhigh
When `Science::Biology::Cells` is collapsed. Searching for `Cells` automatically opens the tree

When tapping `Science`:
* Before, it used the saved state, rather than the visible state, so appeared as a no-op.
* Now, it toggles the displayed state and remembers that the tag was explicitly collapsed.

Part of 10397

Assisted-by: GPT-6-Astra xhigh
Limit saved search names & queries to 500 characters, to prevent ANRs.

Fixes 22820

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6.1-Sol xhigh
Report `UnusedTagNotMoved` when the backend fails to move a tag
subtree as it is unused.

Part of 10397
Workaround for https://redirect.github.com/ankitects/anki/issues/5870

Assisted-by: GPT-6.1-Sol xhigh
Assisted-by: GPT-6-Astra xhigh
Fixes 22827

Assisted-by: GPT-6.1-Sol xhigh
Typing an existing tag in the add tag dialog shows an error. This adds a
test that typing a new tag afterwards clears it and enables OK again.
The dialog setup moves into a helper shared with the existing test.
…0-28

# Conflicts:
#	AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt
Upstream moved Theme_Light and Theme_Dark into :common:android, so the fork's tests that wrap a context in them no longer compiled.
AnkiquestPollWorker from an earlier test can read the profile through this test's server, which made the exact request list fail on ankiquest. The test is about every read carrying the member token, so it now checks which endpoints were read rather than how often.
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

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

  • CardBrowserScreenshotTest: 3 changes
  • CustomStudyScreenshotTest: 2 changes
  • DeckPickerScreenshotTest: 5 changes
  • DeckPickerTabletScreenshotTest: 1 change
  • NavigationDrawerScreenshotTest: 1 change
  • PreferencesScreenshotTest: 2 changes
  • ReviewRemindersScreenshotTest: 5 changes
  • StandardSearchFragmentScreenshotTest: 1 change
  • TextInputDialogScreenshotTest: 4 changes
All 24 changed screenshots

CardBrowserScreenshotTest

  • long_cells_collapsed_again_compare.png
  • long_cells_collapsed_compare.png
  • long_cells_expanded_compare.png

CustomStudyScreenshotTest

  • tablet_unavailable_deck_compare.png
  • unavailable_deck_compare.png

DeckPickerScreenshotTest

  • bottom_navigation_keyboard_open_scrolled_to_bottom_compare.png
  • deck_list_after_dismissing_create_deck_dialog_compare.png
  • hierarchy_lines_collapsed_compare.png
  • hierarchy_lines_many_siblings_compare.png
  • keyboard_open_scrolled_to_bottom_compare.png

DeckPickerTabletScreenshotTest

  • keyboard_open_compare.png

NavigationDrawerScreenshotTest

  • navigationDrawer_compare.png

PreferencesScreenshotTest

  • TimeboxSettingsFragment_compare.png
  • TimeboxSettingsFragment_disabled_compare.png

ReviewRemindersScreenshotTest

  • studyOptionsFragmentHost_scheduleReminders_compare.png
  • studyOptionsFragmentHost_systemBars_compare.png
  • studyOptionsFragmentHost_systemBars_scrolledToEnd_compare.png
  • studyOptionsFragmentHost_troubleshooting_compare.png
  • studyOptionsFragmentHost_troubleshooting_systemBars_compare.png

StandardSearchFragmentScreenshotTest

  • large_saved_search_previews_compare.png

TextInputDialogScreenshotTest

  • add_profile_empty_light_compare.png
  • add_profile_valid_light_compare.png
  • large_text_short_window_input_light_compare.png
  • large_text_short_window_title_light_compare.png

Naming the theme through CommonR took the line past ktlint's 140 columns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants