Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
38f9c23
build(ci): fix CodeQL OOM
david-allison Apr 18, 2026
e8c2f43
fix: error shown when snackbar not anchored
criticalAY Apr 18, 2026
df9dfa9
refactor(deck-picker): combine Context Menu result
david-allison Apr 7, 2026
7495a6e
refactor(deck-picker): instantiating context menus
david-allison Apr 7, 2026
70c79ba
refactor(deck-picker): right click menu
david-allison Apr 7, 2026
ad7db06
refactor(deck-picker): move deck selection to ViewModel
david-allison Apr 7, 2026
d48f6f5
fix(notifications): use app language
david-allison Apr 15, 2026
2668e57
fix: use app language in BroadcastReceivers
david-allison Apr 17, 2026
4a2ccb4
Fix controls: prevent duplicate bindings
manocormen Apr 15, 2026
df3d306
fix(ci): APK Size Comparison
david-allison Apr 19, 2026
c2282e0
Fix `repairCollection` requiring the database to be opened
Alex7D3 Dec 22, 2025
232418d
docs: define 'BrowserColumnKey'
david-allison Jan 27, 2026
ebf128f
feat(card-browser): prepare to replace 'SortType'
david-allison Jan 27, 2026
2295a15
feat(card-browser): support setting new SortType in ViewModel
david-allison Jan 27, 2026
130f3d2
fix: keep deck selection dialog open for multi-select in widget config
pankaj0695 Feb 22, 2026
6b9043b
fix(NoteEditor): move all sibling cards when changing deck in Note mode
KT-1114 Mar 26, 2026
462a759
test: add test for moving all cards of a note to a new deck
KT-1114 Mar 30, 2026
ff65815
Fix: Restore widget recurring alarms on startup
DoomsCoder Mar 23, 2026
538f16d
fix: keep sync snackbar anchored correctly
criticalAY Apr 18, 2026
4152002
feat: define trigger app restart guard
criticalAY Apr 4, 2026
570c05a
feat(api): added fields to cards endpoints
lonewolf2208 Mar 25, 2026
9d33fd8
feat: allow renaming profiles
criticalAY Apr 5, 2026
6f7146e
feat: profile name validation
criticalAY Apr 9, 2026
63b5f86
Updated strings from Crowdin
Apr 19, 2026
82acd19
AudioTimer to use Coroutines and precise time tracking
criticalAY Dec 12, 2025
b04b726
feat(card-template-editor): better menu text
vinay-singh-dev Mar 15, 2026
61dca60
feat(card-template-editor): better dialog titles
david-allison Mar 24, 2026
5d7d4be
feat(card-template-editor): better dialog buttons
david-allison Mar 24, 2026
d55f601
docs: 'Rename card template' -> 'Rename card type'
david-allison Mar 24, 2026
283db1d
fix(rename-card-type): better 'hint' text
david-allison Mar 24, 2026
e635d58
fix(rename-card-type): trim input
david-allison Mar 24, 2026
cd49b40
feat(dialog): add input validation
david-allison Mar 24, 2026
4633095
feat(rename-card-type): validation
david-allison Mar 15, 2026
b722b30
refactor: ConfirmationDialog.kt
david-allison Mar 24, 2026
be0c54a
fix: ConfirmationDialog - support a null title
david-allison Mar 24, 2026
72f33cf
refactor: make ConfirmationDialog message non-null
david-allison Mar 24, 2026
0c50cca
feat(card-browser): add design for 'standard' SearchView
david-allison Jan 19, 2026
dfd88d7
feat(card-browser): perform search from new SearchView
david-allison Mar 5, 2026
3398898
feat(card-browser): handle search history in new SearchView
david-allison Jan 21, 2026
9a4a26e
feat(card-browser): display 'saved searches' in new SearchView
david-allison Jan 21, 2026
859b900
feat(card-browser): search string generation & coloring
david-allison Jan 21, 2026
ebf4cac
feat(card-browser): select deck with a chip
david-allison Jan 22, 2026
cde0f52
feat(card-browser): sync search fragment and main fragment filters
david-allison Jan 22, 2026
3721079
feat(card-browser): select tags with a chip
david-allison Mar 12, 2026
d4211c4
feat(card-browser): use colored filters on 'history' items
david-allison Jan 22, 2026
bf8d664
feat(card-browser): delegate menu to SearchBar
david-allison Jan 22, 2026
0e9c5d9
feat(card-browser): hide 'show marked'
david-allison Mar 8, 2026
4fbcabe
feat(card-browser): 'Card state' filter chip
david-allison Mar 12, 2026
64a00c2
feat(card-browser): move filters to SearchViewModel
david-allison Mar 12, 2026
c170605
feat(card-browser): select flags with a chip
david-allison Jan 26, 2026
82726af
feat(card-browser): add a chip to select sort order
david-allison Jan 26, 2026
5f7acdf
feat(card-browser): theme chip 'selected' state
david-allison Mar 13, 2026
fa4836d
feat(card-browser): show 'preview' before 'add' in new UI
david-allison Mar 14, 2026
580ef0b
feat(card-browser): add icons for non-multiselect options
david-allison Mar 14, 2026
a3a3151
Updated strings from Crowdin
Apr 19, 2026
a06af6b
build: reduce API minSdk to 16
david-allison Apr 19, 2026
514d0a4
build(compat): fix 'Unexpected reference to null'
david-allison Apr 19, 2026
490c6d5
fix(card-browser): 'all columns' selected
david-allison Apr 19, 2026
f946f26
api: add raw card scheduling fields to card provider
lonewolf2208 Apr 6, 2026
9476f24
feat(card-template-editor): introduce FieldFilters
david-allison Mar 20, 2026
d554881
fix(dialog): enable 'positive' button
david-allison Apr 19, 2026
58a6c1f
feat: add profile dialog
criticalAY Apr 5, 2026
5b1c862
docs: opChanges return type issue
david-allison Mar 25, 2026
887d5d9
docs(libanki): document tags return values
david-allison Mar 25, 2026
8ba345e
feat(manage-tags): introduce ViewModel
david-allison Mar 25, 2026
edb305d
refactor(common): replace Timber with SLF4J
david-allison Apr 13, 2026
c34226b
refactor(build): improve JVM opt-in flag usage
david-allison Apr 13, 2026
aa3b5ce
arch: remove Android dependencies from common
david-allison Apr 13, 2026
0da67f4
arch: split ':common:android' from ':common'
david-allison Apr 13, 2026
dde8301
refactor: rename files in 'common'
david-allison Apr 13, 2026
cc07e9f
test: setup `:common:android` for testing
david-allison Apr 13, 2026
8b8c687
feat(reminders): add troubleshooting screen
david-allison Mar 28, 2026
cb2f818
feat(reminders): display troubleshooting checks
david-allison Mar 28, 2026
0224f23
feat(reminders): troubleshooting issue fixes
david-allison Mar 28, 2026
c6c809f
refactor: add initializeAcraCrashReporter() top-level function
criticalAY Apr 17, 2026
c0d6e5c
refctor: extract crash reporting interface to :common:android
criticalAY Apr 17, 2026
d529ff1
refactor: move CrashReportService to crashreporting package
criticalAY Apr 17, 2026
99a6e02
Automate sync_translations workflow for 30 minute delays
LUwUcifer Apr 16, 2026
e6a8f14
feat(api): expose remaining raw card fields
lonewolf2208 Apr 18, 2026
d05cb00
test: add isValidTextOrDataFile MIME type tests
bartonlu Apr 20, 2026
691b693
fix: default analytics opt-in checkbox to unchecked
criticalAY Apr 20, 2026
d74c550
test: add test for saveToggleStickyMap
qingfeng19491001 Apr 18, 2026
cd4a53c
fix(whiteboard): draw smooth curves
david-allison Mar 21, 2026
a4b1b55
fix(reminders): catch BadParcelableException in NotificationService
ericli3690 Apr 20, 2026
22dafd8
fix(reminders): only set alarm if enabled
ericli3690 Apr 20, 2026
3da1cab
Fix FAB visibility after SearchView dismissal on deck deletion
lukstbit Apr 18, 2026
21d58d0
Update baseline profile with DeckPicker onResume journey
Galal-20 Apr 15, 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
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jobs:
shell: bash
run: |
./gradlew assemblePlayRelease -x lintVitalPlayRelease
# Free the Gradle daemon's heap before CodeQL analysis runs, or we OOM the runner (#20768)
./gradlew --stop

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/compare_apk_size.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ jobs:
echo y | keytool -genkeypair -dname "cn=AnkiDroid, ou=ankidroid, o=AnkiDroid, c=US" -alias $KEYALIAS -keypass $KEYPWD -keystore $KEYSTOREPATH -storepass $KEYSTOREPWD -keyalg RSA -validity 20000
shell: bash

- name: Checkout PR
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: 'refs/pull/${{ github.event.inputs.prNumber }}/head'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
timeout-minutes: 5
Expand All @@ -50,12 +56,6 @@ jobs:
cache-provider: basic
cache-read-only: true

- name: Checkout PR
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: 'refs/pull/${{ github.event.inputs.prNumber }}/head'

- name: Assemble PR APK
# This makes sure we fetch gradle network resources with a retry
uses: nick-fields/retry@v4
Expand Down
28 changes: 27 additions & 1 deletion .github/workflows/sync_translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ name: 🌐 Sync Translations
on:
workflow_dispatch:

concurrency:
group: sync-translations
cancel-in-progress: true

# We use a machine account PAT from secrets so workflows are triggered
# the default token is not needed and should be fully restricted
permissions: {}

jobs:
sync_translations:
name: 'Sync Translations with Crowdin'
timeout-minutes: 20
timeout-minutes: 50
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand All @@ -32,6 +36,28 @@ jobs:
git reset --hard origin/main
shell: bash

- name: Get second to latest run time
env:
GH_TOKEN: ${{ secrets.MACHINE_ACCOUNT_PAT }}
run: |
OUTPUT=$(gh run list --workflow sync_translations.yml --json updatedAt | jq -r '.[1].updatedAt')
echo "LATEST_RUN=$OUTPUT" >> $GITHUB_ENV

- name: Calculate seconds passed
id: calc_time
uses: actions/github-script@v8
with:
script: |
const lastRun = new Date(process.env.LATEST_RUN);
const secondsPassed = Math.floor((Date.now() - lastRun.getTime()) / 1000);
const timeLeft = 1800 - secondsPassed > 0 ? 1800 - secondsPassed: 0;
console.log('Seconds to sleep for: ' + timeLeft);
core.setOutput('time_left', timeLeft);

- name: Await CrowdIn API Cooldown If Needed
if: steps.calc_time.outputs.time_left > 0
run: sleep ${{ steps.calc_time.outputs.time_left }}

- uses: actions/setup-node@v6
with:
node-version: 20
Expand Down
1 change: 1 addition & 0 deletions AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ dependencies {

// modules
implementation project(":common")
implementation project(":common:android")
implementation project(":compat")
implementation project(":libanki")
implementation project(":vbpd")
Expand Down
21 changes: 17 additions & 4 deletions AnkiDroid/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ tasks.register('jacocoTestReport', JacocoReport) {
dependsOn("connectedPlay${rootProject.androidTestVariantName}AndroidTest")
}

// modules do not yet support flavors (play/full)
def modulesToUnitTest = [":common", ":libanki", ":compat"]
// Android library modules (do not yet support flavors play/full)
def androidModulesToUnitTest = [":common:android", ":libanki", ":compat"]
// JVM modules: use 'test' task and 'classes/kotlin/main' class dir
def jvmModulesToUnitTest = [":common"]

// A unit-test only report task
tasks.register('jacocoUnitTestReport', JacocoReport) {report ->
Expand All @@ -121,10 +123,15 @@ tasks.register('jacocoUnitTestReport', JacocoReport) {report ->
includeUnitTestCoverage(report, getProject(), flavorClassDir)
dependsOn('testPlayDebugUnitTest')

for (module in modulesToUnitTest) {
for (module in androidModulesToUnitTest) {
includeUnitTestCoverage(report, project(module), moduleClassDir)
dependsOn("${module}:testDebugUnitTest")
}

for (module in jvmModulesToUnitTest) {
includeUnitTestCoverage(report, project(module), 'classes/kotlin/main')
dependsOn("${module}:test")
}
}

gradle.projectsEvaluated {
Expand All @@ -135,12 +142,18 @@ gradle.projectsEvaluated {
task.outputs.cacheIf { false }
}
}
for (module in modulesToUnitTest) {
for (module in androidModulesToUnitTest) {
project(module).tasks.named('testDebugUnitTest')?.configure {task ->
task.outputs.upToDateWhen { false }
task.outputs.cacheIf { false }
}
}
for (module in jvmModulesToUnitTest) {
project(module).tasks.named('test')?.configure {task ->
task.outputs.upToDateWhen { false }
task.outputs.cacheIf { false }
}
}
}


Expand Down
36 changes: 20 additions & 16 deletions AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ import androidx.core.content.edit
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.CrashReportService
import com.ichi2.anki.CrashReportService.FEEDBACK_REPORT_ALWAYS
import com.ichi2.anki.CrashReportService.FEEDBACK_REPORT_ASK
import com.ichi2.anki.R
import com.ichi2.anki.acraCoreConfigBuilder
import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.crashreporting.CrashReporter
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ALWAYS
import com.ichi2.anki.common.crashreporting.CrashReporter.Companion.FEEDBACK_REPORT_ASK
import com.ichi2.anki.logging.ProductionCrashReportingTree
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.servicelayer.ThrowableFilterService
import com.ichi2.anki.setDebugACRAConfig
import com.ichi2.anki.setProductionACRAConfig
import com.ichi2.anki.testutil.GrantStoragePermission
import org.acra.ACRA
import org.acra.builder.ReportBuilder
Expand Down Expand Up @@ -70,7 +74,7 @@ class ACRATest : InstrumentedTest() {
@Throws(Exception::class)
fun testDebugConfiguration() {
// Debug mode overrides all saved state so no setup needed
CrashReportService.setDebugACRAConfig(sharedPrefs)
setDebugACRAConfig(sharedPrefs)
assertArrayEquals(
"Debug logcat arguments not set correctly",
CrashReportService.acraCoreConfigBuilder
Expand All @@ -90,21 +94,21 @@ class ACRATest : InstrumentedTest() {
)
assertEquals(
"ACRA feedback was not turned off correctly",
CrashReportService.FEEDBACK_REPORT_NEVER,
CrashReporter.FEEDBACK_REPORT_NEVER,
sharedPrefs
.getString(CrashReportService.FEEDBACK_REPORT_KEY, "undefined"),
.getString(CrashReporter.FEEDBACK_REPORT_KEY, "undefined"),
)
}

@Test
@Throws(Exception::class)
fun testProductionConfigurationUserDisabled() {
// set up as if the user had prefs saved to disable completely
setReportConfig(CrashReportService.FEEDBACK_REPORT_NEVER)
setReportConfig(CrashReporter.FEEDBACK_REPORT_NEVER)

// ACRA initializes production logcat via annotation and we can't mock Build.DEBUG
// That means we are restricted from verifying production logcat args and this is the debug case again
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)
verifyDebugACRAPreferences()
}

Expand All @@ -115,7 +119,7 @@ class ACRATest : InstrumentedTest() {
setReportConfig(FEEDBACK_REPORT_ASK)

// If the user is set to ask, then it's production, with interaction mode dialog
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()

assertToastMessage(R.string.feedback_for_manual_toast_text)
Expand All @@ -134,7 +138,7 @@ class ACRATest : InstrumentedTest() {

// If the user is set to always, then it's production, with interaction mode toast
// will be useful with ACRA 5.2.0
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)

// The same class/method combo is only sent once, so we face a new method each time (should test that system later)
val crash = Exception("testCrashReportSend at " + System.currentTimeMillis())
Expand Down Expand Up @@ -173,7 +177,7 @@ class ACRATest : InstrumentedTest() {
)

// Now let's clear data
CrashReportService.deleteACRALimiterData(testContext)
CrashReportService.deleteLimiterData(testContext)

// A third send should work again
assertTrue(
Expand All @@ -192,7 +196,7 @@ class ACRATest : InstrumentedTest() {
setReportConfig(FEEDBACK_REPORT_ALWAYS)

// If the user is set to always, then it's production, with interaction mode toast
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()

assertToastMessage(R.string.feedback_auto_toast_text)
Expand All @@ -207,7 +211,7 @@ class ACRATest : InstrumentedTest() {
setReportConfig(FEEDBACK_REPORT_ALWAYS)

// If the user is set to ask, then it's production, with interaction mode dialog
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()

assertDialogEnabledStatus("dialog should be disabled when status is ALWAYS", false)
Expand All @@ -226,7 +230,7 @@ class ACRATest : InstrumentedTest() {
setReportConfig(FEEDBACK_REPORT_ASK)

// If the user is set to ask, then it's production, with interaction mode dialog
CrashReportService.setProductionACRAConfig(sharedPrefs)
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()

assertToastMessage(R.string.feedback_for_manual_toast_text)
Expand Down Expand Up @@ -282,7 +286,7 @@ class ACRATest : InstrumentedTest() {
}

private fun setAcraReportingMode(feedbackReportAlways: String) {
CrashReportService.setAcraReportingMode(feedbackReportAlways)
CrashReportService.setReportingMode(feedbackReportAlways)
}

@Throws(ACRAConfigurationException::class)
Expand Down Expand Up @@ -333,7 +337,7 @@ class ACRATest : InstrumentedTest() {
}

private fun setReportConfig(feedbackReportAsk: String) {
sharedPrefs.edit { putString(CrashReportService.FEEDBACK_REPORT_KEY, feedbackReportAsk) }
sharedPrefs.edit { putString(CrashReporter.FEEDBACK_REPORT_KEY, feedbackReportAsk) }
}

private val sharedPrefs: SharedPreferences
Expand Down
Loading