Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
455 commits
Select commit Hold shift + click to select a range
49b922c
refactor: move snackbar helpers to `:common:android`
criticalAY Oct 22, 2026
8de080a
fix(controls): preserve '+' bindings
david-allison Oct 23, 2026
a6b4654
refactor: separate binding lookup and warning ui
Arthur-Milchior May 12, 2025
ab7d214
perf(lint): disable `UElementAsPsi` for non-lint modules
david-allison Oct 12, 2026
8563f29
refactor(coverage): capture/expose library variants
david-allison Oct 23, 2026
a2b809f
refactor(gradle): coverage implementation
david-allison Oct 22, 2026
5a951b5
fix(gradle): coverage runs on debug variants
david-allison Oct 22, 2026
9e67946
fix(gradle): use JVM source sets for coverage
david-allison Oct 22, 2026
a91158b
fix(gradle): use Android artifacts for coverage
david-allison Oct 22, 2026
8f058df
fix(gradle): restrict JaCoCo data to test task outputs
david-allison Oct 22, 2026
fa7aee2
feat(backend): expose active locales
david-allison Oct 13, 2026
a00ec1f
feat(debug-info): Add locales to 'copy debug info'
Tanay3183 Oct 10, 2026
1fa324c
fix(study-screen): allow multi-finger gestures on clickable card content
Ratnesh5 Oct 14, 2026
fafd484
fix(custom-study): keep chosen tags after the screen is recreated
criticalAY Oct 20, 2026
5a8ff91
fix(congrats): stop Custom Study crashing after the screen is recreated
criticalAY Oct 20, 2026
af39491
fix(custom-study): keep sub-dialogs after process death
criticalAY Oct 22, 2026
9d0f913
test(congrats): shorten the custom study process death test
criticalAY Oct 23, 2026
4b3fec2
fix(card-browser): save its own editor pane width
criticalAY Oct 17, 2026
ae024d4
feat(webview): add Activity support to SafeWebViewLayout
Ratnesh5 Oct 9, 2026
7aa9e51
fix(webview): migrate Info to use SafeWebViewLayout
Ratnesh5 Oct 9, 2026
624e01d
refactor: move dialog helper utilities to `:common:android`
criticalAY Oct 24, 2026
b1a2b44
refactor: move AlertDialog helpers to `:common:android`
criticalAY Oct 24, 2026
212cf6a
refactor: move `DiscardChangesDialog` to `:anki-common`
criticalAY Oct 24, 2026
3a8ca05
fix(lint): check string-array in InvalidStringFormat
SunilKumarPradhan Oct 20, 2026
2339fc4
fix(studyoptions): extend decorative shadow edge-to-edge
Ratnesh5 Oct 23, 2026
17e2772
fix(studyoptions): mirror decorative shadow gradient in RTL
Ratnesh5 Oct 23, 2026
d278b68
fix(deckpicker): handle insets for split view in RTL
Ratnesh5 Oct 23, 2026
e492a44
test(deckpicker): add RTL tablet screenshot test
Ratnesh5 Oct 23, 2026
1722322
fix(custom-study): keep the typed value when another dialog closes
criticalAY Oct 23, 2026
d50598a
refactor(startup): stop depending on IntentHandler in StartupGuards
criticalAY Oct 24, 2026
dfcc53c
refactor(startup): move StartupGuards to `:anki-common`
criticalAY Oct 24, 2026
09f9fa9
test(api): cover parallel provider URIs & constants
david-allison Sep 28, 2026
f1ea354
feat(api): add helpers for parallel build URIs
iamllama Oct 28, 2024
a19654a
fix(api): supports parallel builds
iamllama Oct 26, 2024
8967a95
feat(api): add `Card.getContentUri(authority)`
david-allison Sep 28, 2026
13d929a
refactor: prefix intent actions with `ACTION_`
david-allison Oct 23, 2026
42d251b
lint: enforce `ACTION_` for intent actions
david-allison Oct 23, 2026
236f2af
refactor(settings): extract theme prefs/contract to `:common:android`
david-allison Oct 23, 2026
c2478fa
refactor(theme): move `theme_attrs.xml` to `:common:android`
david-allison Oct 23, 2026
2f8ad4e
refactor(theme): move deck/browser theme attrs to `:common:android`
david-allison Oct 23, 2026
d141060
refactor(theme): move note editor theme attrs to `:common:android`
david-allison Oct 23, 2026
00ec5c9
refactor(theme): move common UI attrs to `:common:android`
david-allison Oct 23, 2026
0942319
fix(browser): handle search feedback when stopped
david-allison Oct 16, 2026
8709f8c
fix(browser): display counts after search actions
david-allison Oct 16, 2026
c6e96d8
fix(browser): display counts after cards/notes mode changes
david-allison Oct 16, 2026
5ae40ba
fix(browser): display counts after note/card edits
david-allison Oct 16, 2026
b39026f
fix(browser): display counts after note editor changes
david-allison Oct 16, 2026
72bc071
Updated strings from Crowdin
Oct 24, 2026
0d48828
refactor(note-editor): add noteTypeId param to `setNote`
david-allison Oct 23, 2026
1e518c3
fix(note-editor): when copying, copy NoteTypeId
Arthur-Milchior Sep 18, 2026
2b76dbe
refactor(note-editor): change source of draft field values
david-allison Oct 23, 2026
a0fabe5
fix(note-editor): retain draft fields when changing note type
david-allison Oct 23, 2026
e0be06a
fix(note-editor): restore hidden draft fields after editor recreation
david-allison Oct 23, 2026
5401608
feat: show dialog if no TTS engine is installed
david-allison Oct 2, 2026
5a9ca32
test(compose): add themed screenshot test base
david-allison Oct 18, 2026
eb82f8f
fix(lint): ignore translated strings in `:common:android`
david-allison Oct 18, 2026
cbf8b00
fix(dev): make dialog preview backgrounds transparent
david-allison Oct 18, 2026
222d357
feat(note-editor): add toolbar layout chooser
david-allison Oct 16, 2026
504bd36
feat(note-editor): use new toolbar layout chooser
david-allison Oct 16, 2026
be8f427
fix(note-editor): toolbar layout - show focus in e-ink mode
david-allison Oct 22, 2026
de688fb
feat(profiles): add a profile from the switch profile screen
criticalAY Oct 14, 2026
bc02ca2
feat(profiles): rename a profile
criticalAY Oct 14, 2026
b338399
fix(profiles): don't log a new profile's name
criticalAY Oct 16, 2026
7cc342b
docs(profiles): note the name check is stricter than Desktop
criticalAY Oct 16, 2026
684ba05
build(deps): bump undici from 6.29.0 to 6.29.1 in /tools/localization
dependabot[bot] Oct 24, 2026
77c148d
fix(shared-decks): offer log in and sign up when AnkiWeb asks for a l…
criticalAY Oct 5, 2026
693b477
test(shared-decks): screenshot the AnkiWeb login dialog
criticalAY Oct 14, 2026
74ff3c5
fix(reviewer): fix delayed answer feedback animation
david-allison Oct 24, 2026
2865630
refactor(import): extract `openCsvImporter`
david-allison Oct 20, 2026
b8dd0af
fix(import): expose text file copy failures
david-allison Oct 20, 2026
4c74cba
refactor(import): return source & path on success
david-allison Oct 20, 2026
3dd0dad
refactor(import): extract cacheImport
david-allison Oct 20, 2026
3fd8881
refactor(import): expose `prepareImport`
david-allison Oct 20, 2026
9801213
refactor(import): UI methods require `Activity`
david-allison Oct 21, 2026
6b42561
refactor(import): extract openImport
david-allison Oct 21, 2026
a428146
refactor(import): only resolve file once
david-allison Oct 21, 2026
5e9ca41
feat(preferences): add Compose timebox settings subscreen with Materi…
Ratnesh5 Oct 12, 2026
005ed67
feat(preferences): use text field for timebox limit
Ratnesh5 Oct 23, 2026
7ca100e
feat(preferences): inline unit in timebox stepper
Ratnesh5 Oct 23, 2026
4aacb63
fix(preferences): stepper delta, align, and insets
Ratnesh5 Oct 23, 2026
622282f
fix(preferences): loading state for timebox screen
Ratnesh5 Oct 23, 2026
ede1dfe
fix(preferences): reorder modifier in screen
Ratnesh5 Oct 24, 2026
7f55aa5
fix(preferences): address timebox screen review feedback
Ratnesh5 Oct 24, 2026
c1fe1ec
fix(preferences): use plurals for timebox suffix and enlarge stepper …
Ratnesh5 Oct 24, 2026
c628a27
fix(preferences): keep uniform card container color and avoid nav bar…
Ratnesh5 Oct 24, 2026
73ce387
fix(browser): stop UI work when view is destroyed
david-allison Oct 24, 2026
afd924a
fix(filtered-decks): use the view lifecycle to collect flows
david-allison Oct 24, 2026
f2686e8
fix(filtered-decks): bind input listeners after view restoration
david-allison Oct 24, 2026
4884abb
fix(reminders): stop UI work the view is destroyed
david-allison Oct 24, 2026
32e7fe0
fix(reminders): stop work when fragment is removed
david-allison Oct 24, 2026
90431bc
refactor(theme): move theme palette colors to `:common:android`
david-allison Oct 24, 2026
e83a292
refactor(theme): move text color selectors to `:common:android`
david-allison Oct 24, 2026
947691d
refactor(theme): move reviewer footer drawables to `:common:android`
david-allison Oct 24, 2026
95f76b0
refactor(theme): move navigation theme assets to `:common:android`
david-allison Oct 24, 2026
6dc4c02
refactor(theme): move ui control styles to `:common:android`
david-allison Oct 24, 2026
42f656f
refactor(collection): add `withClosedCollection` utility
david-allison Oct 24, 2026
ffe2f81
refactor(permissions): extract `configurePermissions`
david-allison Oct 24, 2026
6e03892
refactor(permissions): extract `showPermissionsFragment`
david-allison Oct 24, 2026
ae312f5
test(permissions): grant permissions for 'Continue' case
david-allison Oct 24, 2026
b0e89c8
test(permissions): extract `PermissionsActivityScreenshotTest`
david-allison Oct 24, 2026
98c50ad
refactor(custom-study): inflate menu using XML
david-allison Oct 22, 2026
85f0635
fix(custom-study): add loading state
david-allison Oct 22, 2026
219dfaf
fix(custom-study): asynchronously load menu
david-allison Oct 22, 2026
0f0b274
fix(custom-study): handle empty and hidden decks
david-allison Oct 22, 2026
9c96397
fix(custom-study): handle stale menu selections
david-allison Oct 22, 2026
bb30a38
fix(custom-study): cancel initialization on dialog close
david-allison Oct 23, 2026
5fe4950
fix(custom-study): ignore concurrent menu selections
david-allison Oct 23, 2026
5672808
feat(multimedia): external image editing helpers
david-allison Oct 11, 2026
fe2acfc
feat(multimedia): connect external editors to image actions
david-allison Oct 11, 2026
96fed4a
fix(multimedia): add MediaStore.EXTRA_OUTPUT for Google Photos
david-allison Oct 11, 2026
b89938f
test: coverage for RepositionCardTemplateDialog
david-allison Oct 23, 2026
9693d19
refactor: leaner card template ordinal tracking
david-allison Oct 23, 2026
a7121c1
feat(card-templates): reposition card types
david-allison Oct 23, 2026
d152169
pref(test): use preview translations instead of mocks
david-allison Oct 25, 2026
650d143
Improve extend daily limits dialogs
Roniscend Sep 11, 2026
4e438a2
fix(lint): ignore translated strings in `:common:android`
david-allison Oct 18, 2026
7aa1f85
refactor: use 'Deck' accessors
Arthur-Milchior Jan 8, 2025
6931ba2
refactor: make 'Deck' wrap its JSONObject
Arthur-Milchior Jan 8, 2025
6112081
refactor: delegate & document Deck properties
Arthur-Milchior Jan 8, 2025
5fc65ab
refactor: strongly type fields of 'Deck'
Arthur-Milchior Jan 8, 2025
a15eba6
refactor: extract JSONObject.getLongOrNull to `:common`
david-allison Oct 6, 2026
67491d9
refactor: add filtered deck search terms accessor
Arthur-Milchior Jan 8, 2025
e677ff8
docs: link AI_POLICY in welcome bot comments
david-allison Oct 24, 2026
46f29bd
docs: relax GitHub AI disclosure formatting
david-allison Oct 24, 2026
e1180ba
docs: allow AI-assisted code review comments
david-allison Oct 24, 2026
4ef05f5
docs: simplify AI tool disclosure in commit messages
david-allison Oct 24, 2026
52eae6c
docs: allow AI-produced issues at maintainer discretion
david-allison Oct 24, 2026
3256094
Improve screenshot test output folder format
Phoenix1808 Oct 23, 2026
86e24bd
Updated strings from Crowdin
Oct 25, 2026
fe65770
Bumped version to 2.26.0alpha8
mikehardy Oct 25, 2026
0efacb2
refactor(errors): open help destinations through the navigator
criticalAY Oct 25, 2026
f41b7b8
design(preferences): reduce 'blue' in timebox settings
david-allison Oct 25, 2026
50d6999
design(preferences): improve timebox contrast on e-ink
david-allison Oct 25, 2026
44505a9
fix(theme): give Black theme bottom sheets the dialog colour
criticalAY Oct 25, 2026
c22d356
refactor: move deck selection helpers out of :AnkiDroid
criticalAY Oct 25, 2026
2b8f6af
refactor(startup): prep for making `MANAGE_EXTERNAL_STORAGE` skippable
david-allison Oct 25, 2026
0fb9f43
refactor(startup): rename 'ensureCollectionPathSet'
david-allison Oct 25, 2026
a90aa4c
feat(startup): defer the storage decision if public storage is inacce…
david-allison Aug 16, 2026
50cc545
docs: source `ic_sort_pill_arrow_down_label`
david-allison Oct 24, 2026
75e3354
docs: source `ic_bar_chart_black`
david-allison Oct 24, 2026
8357a6c
docs: source `ic_flashcard_black`
david-allison Oct 24, 2026
802b124
docs: source `ic_view_list_white_24dp`
david-allison Oct 24, 2026
705220d
docs: source `ic_add_equation_black_24dp`
david-allison Oct 24, 2026
6b2dfdc
fix(ci): add swap to Linux unit test runners
criticalAY Oct 26, 2026
841586c
refactor: explicit backing fields in AddEditReminderDialogViewModel
ericli3690 Oct 2, 2026
6efd8a6
fix(reminders): migrate should strictly point to next schema
ericli3690 Oct 3, 2026
e025876
fix(reminders): keep id upon migration
ericli3690 Oct 3, 2026
2fc33b4
fix(reminders): do not persist cachedDeckName
ericli3690 Oct 4, 2026
dc81333
feat(reminders): threshold filters schema migration
ericli3690 Oct 4, 2026
22b6779
feat(reminders): threshold filters UI
ericli3690 Oct 5, 2026
0860097
feat(reminders): apply threshold filters at notification time
ericli3690 Oct 5, 2026
d00f4f2
docs(reminders): add threshold filter usage examples
ericli3690 Oct 6, 2026
0879c5d
docs: source `ic_horizontal_rule_black_24dp`
david-allison Oct 26, 2026
e121467
docs: source `calendar_date_range`
david-allison Oct 26, 2026
25bb233
docs: source `calendar_single_day`
david-allison Oct 26, 2026
3b6d671
docs: source `ic_restore_variant`
david-allison Oct 26, 2026
1c0d725
fix(note-editor): stop rebuilding the editor when closing after save
david-allison Oct 25, 2026
b301a51
feat(note-editor): 'shares' to AnkiDroid persist a separate deck
david-allison Oct 25, 2026
26ca314
fix(manage-space): stop collectors when the view is destroyed
david-allison Oct 26, 2026
831f4df
test: remove 'knownFailures' lifecycle option
david-allison Oct 26, 2026
e38698f
Updated strings from Crowdin
Oct 26, 2026
b67ae2d
docs: fix PR template link in CONTRIBUTING
swinilohani616-dot Oct 26, 2026
258611a
test: reset backend locales in LanguageDebugInfoTest setUp
mikehardy Oct 26, 2026
c1236f8
build(baselineprofile): turn on startup profiles
criticalAY Oct 26, 2026
f4ab6b4
chore(baselineprofile): regenerate both profiles
criticalAY Oct 26, 2026
2f40098
design(theme): use non-blue secondary text colors
david-allison Oct 25, 2026
7226b60
build(deps): adopt the new `anki-backend-android` backend
david-allison Oct 26, 2026
ff9ea08
perf(build): remove `jetbrains.annotation` overrides from compiler cl…
david-allison Oct 26, 2026
aac4ec1
perf(ci): shut down idle Kotlin compilers
david-allison Oct 26, 2026
2f55474
fix(permissions): ensure permission icons are visible
david-allison Oct 26, 2026
0cb3543
fix(browser): skip search updates while finishing
david-allison Oct 26, 2026
4c4c634
refactor(custom-study): separate collection work and dialog updates
david-allison Oct 26, 2026
789782b
fix(custom-study): complete operations after dialog dismissal
david-allison Oct 26, 2026
61a2f0b
fix(crash-reporting): use stable fragment names
david-allison Oct 26, 2026
251c515
feat(ci): show full `/buildSrc` test failures
david-allison Oct 26, 2026
504e893
fix(deck-picker): fix crash for studying a hidden 'Default' deck
david-allison Oct 20, 2026
f1d5153
refactor: move the text input dialog layout to `:common:android`
criticalAY Oct 26, 2026
813a883
refactor: move `CreateDeckDialog` to `:anki-common`
criticalAY Oct 26, 2026
4fc7d43
fix(profiles): don't count surrounding spaces in the name counter
criticalAY Oct 23, 2026
5699d49
Bumped version to 2.26.0alpha9
mikehardy Oct 27, 2026
5bb75ad
perf(ci): skip conflict scans for tag pushes
david-allison Oct 27, 2026
4f4a2c4
pref(ci): skip conflict scans for merge queue pushes
david-allison Oct 27, 2026
e4acb38
perf(ci): skip squash merge runner for merge groups
david-allison Oct 27, 2026
cee8622
fix(shared-decks): return to the deck after an AnkiWeb login
criticalAY Oct 21, 2026
4467ae3
fix(shared-decks): update Back after the home button clears history
criticalAY Oct 26, 2026
9d3bc91
refactor(theme): move dark/black theme definitions to `:common:android`
david-allison Oct 26, 2026
554550a
refactor(theme): move light theme definition to `:common:android`
david-allison Oct 26, 2026
06b879b
refactor(theme): move plain theme definition to `:common:android`
david-allison Oct 26, 2026
e7527ad
refactor(theme): move e-ink theme definition to `:common:android`
david-allison Oct 26, 2026
cfa28e2
refactor(settings): move day/night theme enums to `:common:android`
david-allison Oct 26, 2026
c489157
fix(progress): observe on the fragment's lifecycle
criticalAY Oct 14, 2026
6ac61fa
fix(progress): share a loading dialog between screens
criticalAY Oct 15, 2026
04a4851
feat(study-options): show progress from the ViewModel
criticalAY Oct 14, 2026
5096fbd
fix(tests): keep LeakCanary off under Robolectric
criticalAY Oct 27, 2026
472b98b
refactor: move `ic_remove` to common:android
david-allison Oct 27, 2026
5f4c547
refactor: move `NonLeadingZeroInputFilter` to common:android
david-allison Oct 27, 2026
2f614ec
refactor: move padding helpers to `common:android`
david-allison Oct 27, 2026
c8c2d64
refactor: move `numberRangeTitle()` to `:common:android`
david-allison Oct 27, 2026
5068b77
refactor(compose): extract theme attributes to `:common:android`
david-allison Oct 27, 2026
af08e31
refactor(compose): extract theme/preview helpers to `:common:android`
david-allison Oct 27, 2026
4561666
refactor(note-editor): extract toolbar previews to `:note-editor`
david-allison Oct 27, 2026
ef558bc
test: null external storage without replacing ContextImpl shadow
mikehardy Oct 28, 2026
b0f7dc4
test(tags): Cover existing tags in TagsDialog.kt
Rizos-Vassili Oct 28, 2026
b4275ef
fix: use profile storage for IdsFile
david-allison Oct 5, 2026
141a131
fix: move IdsFile to `cacheDir/idsfiles`
david-allison Oct 5, 2026
aeea59b
fix(app): improve cache cleanup failure handling
david-allison Oct 23, 2026
4c0fe70
feat(app): clean up IdsFile on startup
david-allison Oct 5, 2026
81dbe2a
fix(browser): stop reporting missing IdsFile to ACRA
david-allison Oct 23, 2026
e78676b
refactor: use IdsFile.getIdsOrNull()
david-allison Oct 23, 2026
05e39c2
fix(server): validate content-length before allocating
BrayanDSO Oct 26, 2026
bbc41fa
fix(server): never report a failed request as 200 OK
BrayanDSO Oct 28, 2026
cd96e91
fix(server): reject unhandled methods with 405
BrayanDSO Oct 28, 2026
c70d926
refactor(server): send fixed-length responses
BrayanDSO Oct 28, 2026
38dc16d
fix(compose): make popup menus opaque
david-allison Oct 24, 2026
9369f3a
refactor(compose): inline `TextInputDialogCard`
david-allison Oct 24, 2026
463ca5b
fix(compose): maintain text dialog cursor and selection
david-allison Oct 24, 2026
76ded7f
feat(input-dialog): support input transformations
david-allison Oct 24, 2026
a7b3d52
feat(input-dialog): support helper text
david-allison Oct 24, 2026
4702bed
feat(input-dialog): support 2-line descriptions
david-allison Oct 24, 2026
203a8f2
feat(input-dialog): support validating text submission
david-allison Oct 24, 2026
ba8d871
feat(input-dialog): support 'enabled/disabled' state
david-allison Oct 24, 2026
17dd305
fix(input-dialog): make labels accessible
david-allison Oct 24, 2026
1fb9049
fix(input-dialog): ensure buttons are visible
david-allison Oct 24, 2026
8bc4866
fix(dialogs): remove motion in animations
david-allison Oct 25, 2026
ec88885
fix(tags): preserve casing in row equality for renames
david-allison Oct 24, 2026
b043592
fix(tags): searches no longer expand subtrees towards leaves
david-allison Oct 25, 2026
7a96e24
feat(tags): optimistic updates of 'collapsed' state
david-allison Oct 24, 2026
5ced01e
fix(tags): better handle 'collapse/expand' during search
david-allison Oct 27, 2026
47010a8
fix(browser): ANR on saved saved search previews
david-allison Oct 27, 2026
d06fb5d
build: bump backend to 0.1.74-anki26.09.3
david-allison Oct 28, 2026
2a3381f
fix(tags): workaround: report tag moves which are no-ops
david-allison Oct 24, 2026
1cd42b5
fix(browser): use simple line-break mode to fix ANR
david-allison Oct 27, 2026
9bf725b
test(tags): cover clearing the duplicate tag error
Rizos-Vassili Oct 28, 2026
4c64bfc
refactor: move deck list dimens and toolbar styles to `:common:android`
criticalAY Oct 28, 2026
6bbdc1e
feat(instant-editor): show progress from the ViewModel
criticalAY Oct 24, 2026
c66d67d
fix(instant-editor): ignore Save while saving
criticalAY Oct 28, 2026
6a8dbda
fix(tests): ensure reminder tests check for review completion
david-allison Oct 27, 2026
c80b043
fix(tests): fix day rollover time for notification tests
david-allison Oct 27, 2026
4374aca
refactor: move `appName` to `:common`
criticalAY Oct 27, 2026
7cef1dc
refactor: move `VersionUtils` to `:anki-common`
criticalAY Oct 27, 2026
aadc2b3
refactor: move `openUrl` and the clipboard helpers to `:anki-common`
criticalAY Oct 27, 2026
71bbbf7
refactor: move import UI launch to `Activity.openImport`
david-allison Oct 2, 2026
9cfa1e4
refactor: unify file import handling
david-allison Oct 2, 2026
b441918
refactor: add unified `onSelectedFileToImport`
david-allison Oct 2, 2026
0b6eef6
refactor: drag & drop files use a unified import flow
david-allison Oct 2, 2026
b34d7a1
refactor: process imports on background thread
david-allison Oct 2, 2026
1337ee1
fix(import): prefer filenames over MIME type hints
david-allison Oct 2, 2026
fd5e339
fix(import): prioritize specific package MIME hints
david-allison Oct 27, 2026
9e17e33
ci(emulator): add --stacktrace to the smoke build
criticalAY Oct 27, 2026
9015ffb
ci(emulator): give the smoke build more Gradle heap
criticalAY Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 21 additions & 11 deletions .github/check_alpha_release.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@
const fs = require("node:fs");

module.exports = async function checkAlphaRelease({ github, context, core }) {
async function skipRelease(reason) {
core.info(`Skipping scheduled release: ${reason}`);
await github.rest.actions.cancelWorkflowRun({
...context.repo,
run_id: context.runId,
});
// Cancellation is asynchronous. Keep this step alive so cancelWorkflowRun can kill it.
await keepProcessAlive();
}

const metadata = JSON.parse(
fs.readFileSync("AnkiDroid/build/outputs/release-metadata.json", "utf8"),
);
if (metadata.releaseType !== "alpha") {
core.info("Skipping scheduled release: main is not on an alpha version.");
return false;
return await skipRelease("main is not on an alpha version.");
}

const releases = await github.paginate(github.rest.repos.listReleases, {
Expand All @@ -22,33 +31,34 @@ module.exports = async function checkAlphaRelease({ github, context, core }) {
release => !release.draft && today - Math.floor(Date.parse(release.published_at) / day) < 7,
);
if (recentRelease) {
core.info(
`Skipping scheduled release: ${recentRelease.tag_name} was published fewer than seven UTC calendar days ago.`,
return await skipRelease(
`${recentRelease.tag_name} was published fewer than seven UTC calendar days ago.`,
);
return false;
}

const latestAlpha = releases
.filter(release => !release.draft && /^v\d+\.\d+\.\d+alpha\d+$/.test(release.tag_name))
.sort((a, b) => Date.parse(b.published_at) - Date.parse(a.published_at))[0];
if (!latestAlpha) {
core.info("Skipping scheduled release: no published alpha to compare against.");
return false;
return await skipRelease("no published alpha to compare against.");
}

const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
...context.repo,
basehead: `${latestAlpha.tag_name}...${context.sha}`,
});
if (comparison.status !== "ahead" || comparison.ahead_by === 0) {
core.info(
`Skipping scheduled release: no new commits descending from ${latestAlpha.tag_name}.`,
);
return false;
return await skipRelease(`no new commits descending from ${latestAlpha.tag_name}.`);
}

core.info(
`Publishing an alpha: ${comparison.ahead_by} new commits since ${latestAlpha.tag_name}.`,
);
return true;
};

async function keepProcessAlive() {
// Fail after five minutes if cancellation never arrives.
await new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000));
throw new Error("Timed out waiting for workflow cancellation.");
}
2 changes: 2 additions & 0 deletions .github/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Hello! 👋 Thanks for logging this issue. Please remember we are all volunteers here, so some patience may be required before we can get to the issue. Also remember that the fastest way to get resolution on an issue is to propose a change directly, https://github.com/ankidroid/Anki-Android/wiki/Contributing
Please read our [AI Policy](https://github.com/ankidroid/Anki-Android/blob/main/AI_POLICY.md) before contributing.

# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome

# Comment to be posted to on PRs from first time contributors in your repository
newPRWelcomeComment: >
First PR! 🚀 We sincerely appreciate that you have taken the time to propose a change to AnkiDroid! Please have patience with us as we are all volunteers - we will get to this as soon as possible.
Please ensure your contribution meets our [AI Policy](https://github.com/ankidroid/Anki-Android/blob/main/AI_POLICY.md) before contributing.

# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge

Expand Down
97 changes: 89 additions & 8 deletions .github/report_test_failures.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later

"""Report failures and skipped tests from JUnit XML using the standard library."""
"""Report failures, skipped tests, and overview summary from JUnit XML using the standard library."""

import argparse
import html
Expand All @@ -12,6 +12,19 @@
import xml.etree.ElementTree as ET


TABLE_HEADER = (
"## Test Results\n\n"
"| Suite | Result | Duration | Tests | Passed | Failed | Skipped |\n"
"| ----- | ------ | -------- | ----- | ------ | ------ | ------- |\n"
)


def format_duration(seconds):
minutes = int(seconds // 60)
rem_seconds = seconds % 60
return f"{minutes:02d}m {rem_seconds:06.3f}s"


def read_ignored_tests(log_file):
ignored = set()
failed = set()
Expand All @@ -26,22 +39,61 @@ def read_ignored_tests(log_file):
return ignored - failed


def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures"):
def get_report_duration(root):
suites = root.findall("testsuite")
if suites:
total = 0.0
for s in suites:
try:
total += float(s.get("time", 0) or 0)
except (ValueError, TypeError):
pass
if total > 0.0:
return total
try:
root_time = float(root.get("time", 0) or 0)
if root_time > 0.0:
return root_time
except (ValueError, TypeError):
pass
total = 0.0
for tc in root.iter("testcase"):
try:
total += float(tc.get("time", 0) or 0)
except (ValueError, TypeError):
pass
return total


def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures", suite=None):
failures = []
skipped = []
ignored = read_ignored_tests(log_file)
total_duration = 0.0
passed_count = 0
failed_count = 0
skipped_count = 0

for report in sorted(results_dir.rglob(pattern)):
try:
root = ET.parse(report).getroot()
except (ET.ParseError, OSError) as error:
failures.append(f"Could not read {report}: {error}")
if os.environ.get("GITHUB_ACTIONS") == "true":
print(f"::warning::Could not read {report}: {error}")
else:
print(f"Warning: Could not read {report}: {error}", file=sys.stderr)
continue

total_duration += get_report_duration(root)

for test in root.iter("testcase"):
name = f"{test.get('classname', '')}.{test.get('name', '')}"
has_failure = False
has_skipped = False

for result in test:
if result.tag not in ("failure", "error", "skipped"):
continue
name = f"{test.get('classname', '')}.{test.get('name', '')}"
details = "".join(result.itertext()).strip()
message = result.get("message", "")
# Android can report assumption violations as failures or errors.
Expand All @@ -56,10 +108,30 @@ def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures"
"org.junit.AssumptionViolatedException",
"org.junit.internal.AssumptionViolatedException",
):
has_skipped = True
skipped.append(f"{name}\n{details or result.get('type', 'Test skipped')}")
else:
has_failure = True
failures.append(f"{name}\n{details or result.get('type', 'Test failed')}")

if has_failure:
failed_count += 1
elif has_skipped:
skipped_count += 1
else:
passed_count += 1

total_tests = passed_count + failed_count + skipped_count
is_successful = failed_count == 0 and total_tests > 0
result_text = "SUCCESS" if is_successful else "FAILURE"
elapsed = format_duration(total_duration)

if suite:
print(
f"{suite}: {result_text} ({elapsed}) - "
f"{total_tests} tests: {passed_count} passed, {failed_count} failed, {skipped_count} skipped"
)

for failure in failures:
if os.environ.get("GITHUB_ACTIONS") == "true":
# GitHub renders the escaped message as a multiline error in the log.
Expand All @@ -79,19 +151,27 @@ def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures"
if os.environ.get("GITHUB_ACTIONS") == "true":
print("::endgroup::")

if (failures or skipped) and os.environ.get("GITHUB_STEP_SUMMARY"):
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
if os.environ.get("GITHUB_STEP_SUMMARY"):
with open(os.environ["GITHUB_STEP_SUMMARY"], "a+", encoding="utf-8") as summary:
summary.seek(0)
existing_content = summary.read()
if suite:
if "## Test Results" not in existing_content:
summary.write(TABLE_HEADER)
summary.write(f"| {suite} | **{result_text}** | {elapsed} | {total_tests} | {passed_count} | {failed_count} | {skipped_count} |\n\n")

if failures:
summary.write(f"### {title}\n\n")
for failure in failures:
summary.write(f"<pre>{html.escape(failure)}</pre>\n\n")
if skipped:
skipped_title = f"Skipped tests ({len(skipped)})"
summary.write(f"<details><summary>{skipped_title}</summary>\n\n")
for skip in skipped:
summary.write(f"<pre>{html.escape(skip)}</pre>\n\n")
summary.write("</details>\n\n")

return 1 if failures else 0
return 1 if (failures or (suite and not is_successful)) else 0


if __name__ == "__main__":
Expand All @@ -100,5 +180,6 @@ def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures"
parser.add_argument("log_file", type=Path, nargs="?")
parser.add_argument("--pattern", default="TEST-*.xml", help="Recursive report glob relative to results_dir")
parser.add_argument("--title", default="Test failures", help="Failure heading in the job summary")
parser.add_argument("--suite", help="Suite name for markdown summary table")
args = parser.parse_args()
sys.exit(main(args.results_dir, args.log_file, args.pattern, args.title))
sys.exit(main(args.results_dir, args.log_file, args.pattern, args.title, args.suite))
27 changes: 27 additions & 0 deletions .github/with_emulator_cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later

set -euo pipefail

# Only for dedicated Linux CI runners with one emulator and -no-snapshot-save.
# Forced shutdown must not interrupt saving a snapshot for subsequent jobs.
stop_emulator() {
# #22613 `emu kill` can report success while QEMU keeps running and
# holds the emulator action's output pipes open. Bound both ADB and QEMU.
timeout -s KILL 5s "$ANDROID_HOME/platform-tools/adb" -s "emulator-$EMULATOR_PORT" emu kill || true

local emulator_pattern="^$ANDROID_HOME/emulator/qemu/"
local deadline=$((SECONDS + 20))
while pgrep -f "$emulator_pattern" > /dev/null; do
if ((SECONDS >= deadline)); then
echo "Emulator did not stop within 20 seconds; killing QEMU." >&2
pkill -KILL -f "$emulator_pattern" || true
break
fi
sleep 1
done
}

# Preserve the exit status
trap stop_emulator EXIT
"$@"
3 changes: 3 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ on:
schedule:
- cron: '42 12 * * 1'

env:
JAVA_TOOL_OPTIONS: -Dkotlin.daemon.options=autoshutdownIdleSeconds=10

jobs:
analyze:
name: Analyze (${{ matrix.language }})
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/compare_apk_size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ permissions:
pull-requests: write


env:
JAVA_TOOL_OPTIONS: -Dkotlin.daemon.options=autoshutdownIdleSeconds=10

jobs:
sizeCheck:
name: APK Size Check
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/conflict.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: 🛠️ Conflict Scan

on:
push:
branches-ignore:
- 'gh-readonly-queue/**'
schedule:
- cron: "0 * * * *"

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
retries: 3
script: |
let removeLabelsList = [
"Pending Merge",
Expand Down Expand Up @@ -42,6 +43,7 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
request: { retries: 0 },
body: `Maintainers: Please [Sync Translations](https://github.com/ankidroid/Anki-Android/actions/workflows/sync_translations.yml) to produce a commit with only the automated changes from this PR.

Read more about updating strings on the wiki,
Expand Down Expand Up @@ -80,6 +82,7 @@ jobs:
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
retries: 3
script: |
const I18N_FILES = [
"01-core",
Expand Down Expand Up @@ -133,6 +136,7 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
request: { retries: 0 },
body: `> [!IMPORTANT]
> **Maintainers**: This PR contains https://github.com/ankidroid/Anki-Android/labels/Strings changes

Expand Down Expand Up @@ -207,6 +211,7 @@ jobs:
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
retries: 3
script: |
const creator = context.payload.sender.login
const opts = github.rest.issues.listForRepo.endpoint.merge({
Expand Down Expand Up @@ -240,6 +245,7 @@ jobs:
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
retries: 3
script: |
await github.rest.issues.addLabels({
issue_number: context.issue.number,
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
JAVA_TOOL_OPTIONS: -Dkotlin.daemon.options=autoshutdownIdleSeconds=10

jobs:
lintKotlin:
name: Lint Kotlin
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/merge_requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ on:

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Don't cancel runs: this is a fast check which shows as a failing check when canceled.

jobs:
squashMerge:
name: Squash merge
# perf: stops 'merge_group' from allocating a runner
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

env:
JAVA_TOOL_OPTIONS: -Dkotlin.daemon.options=autoshutdownIdleSeconds=10

jobs:
check_release:
name: 'Check whether a release is due'
permissions:
contents: read
actions: write # Cancel this run when a scheduled publish is skipped.
if: github.event_name == 'schedule' && github.repository == 'ankidroid/Anki-Android' && github.ref == 'refs/heads/main'
timeout-minutes: 20
runs-on: ubuntu-latest
Expand Down
Loading
Loading