From e3ad1ad2232ca0cd7fc1a2d08260848b07d3e022 Mon Sep 17 00:00:00 2001 From: andresmr Date: Wed, 8 Jul 2026 09:19:30 +0200 Subject: [PATCH 01/23] fix: apply lint to false to set a fix version --- aggregates/build.gradle.kts | 2 +- build.gradle.kts | 1 + commonskmm/build.gradle.kts | 2 +- gradle/libs.versions.toml | 1 + login/build.gradle.kts | 2 +- sync/build.gradle.kts | 2 +- tracker/build.gradle.kts | 2 +- 7 files changed, 7 insertions(+), 5 deletions(-) diff --git a/aggregates/build.gradle.kts b/aggregates/build.gradle.kts index 6243cf76e34..1dc8646f8bb 100644 --- a/aggregates/build.gradle.kts +++ b/aggregates/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } kotlin { - androidLibrary { + android { namespace = "org.dhis2.mobile.aggregates" compileSdk = libs.versions.sdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt() diff --git a/build.gradle.kts b/build.gradle.kts index a923bc4363b..37600610e17 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ plugins { alias(libs.plugins.cyclonedx) alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.android.kotlin.multiplatform.library) apply false + alias(libs.plugins.android.lint) apply false } diff --git a/commonskmm/build.gradle.kts b/commonskmm/build.gradle.kts index be788975ab8..015dae0bd8d 100644 --- a/commonskmm/build.gradle.kts +++ b/commonskmm/build.gradle.kts @@ -15,7 +15,7 @@ kotlin { freeCompilerArgs.add("-Xexpect-actual-classes") } - androidLibrary { + android { namespace = "org.dhis2.mobile.commons" compileSdk = libs.versions.sdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f01cf784327..f562b7dcc13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -239,6 +239,7 @@ ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } sentry = { id = "io.sentry.android.gradle", version.ref = "sentryPlugin" } cyclonedx = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" } +android-lint = { id = "com.android.lint", version.ref = "gradle" } [bundles] analytics-implementation = ["androidx-cardview", "androidx-constraintlayout"] diff --git a/login/build.gradle.kts b/login/build.gradle.kts index 380aa71c941..a71bbc9d835 100644 --- a/login/build.gradle.kts +++ b/login/build.gradle.kts @@ -12,7 +12,7 @@ kotlin { freeCompilerArgs.add("-Xcontext-parameters") } - androidLibrary { + android { namespace = "org.dhis2.mobile.login" compileSdk = libs.versions.sdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt() diff --git a/sync/build.gradle.kts b/sync/build.gradle.kts index 6ec44b2d670..fdcc9f41b33 100644 --- a/sync/build.gradle.kts +++ b/sync/build.gradle.kts @@ -15,7 +15,7 @@ kotlin { freeCompilerArgs.add("-Xexpect-actual-classes") } - androidLibrary { + android { namespace = "org.dhis2.mobile.sync" compileSdk = libs.versions.sdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt() diff --git a/tracker/build.gradle.kts b/tracker/build.gradle.kts index ae21c54eb6d..91ad22bd9af 100644 --- a/tracker/build.gradle.kts +++ b/tracker/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } kotlin { - androidLibrary { + android { namespace = "org.dhis2.mobile.tracker" compileSdk = libs.versions.sdk.get().toInt() minSdk = libs.versions.minSdk.get().toInt() From 66f90c3b31b7e32aff7eec1828df36eb7286ea9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manu=20Mu=C3=B1oz?= Date: Tue, 21 Jul 2026 14:08:20 +0200 Subject: [PATCH 02/23] fix: [ANDROAPP-7711] snackbar view context (#4978) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manu Muñoz --- app/src/main/java/org/dhis2/usescases/main/MainActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt b/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt index 988e45bba5c..6ae07907dc9 100644 --- a/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt @@ -304,10 +304,9 @@ class MainActivity : ActivityGlobalAbstract() { } }, ).onNoConnectionListener { - val contextView = findViewById(R.id.navigationBar) Snackbar .make( - contextView, + binding.root, R.string.sync_offline_check_connection, Snackbar.LENGTH_SHORT, ).show() From 8b91e0807334ba06a4f00c9ed08d62351b6dc6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manu=20Mu=C3=B1oz?= Date: Wed, 22 Jul 2026 09:49:17 +0200 Subject: [PATCH 03/23] fix: [ANDROAPP-7708] snackbar view context in granular sync (#4979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manu Muñoz --- .../java/org/dhis2/usescases/main/program/ProgramFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt b/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt index 6340a06b260..80fd6df804a 100644 --- a/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt @@ -161,10 +161,9 @@ class ProgramFragment : } }, ).onNoConnectionListener { - val contextView = activity?.findViewById(R.id.navigationBar) Snackbar .make( - contextView!!, + requireView(), R.string.sync_offline_check_connection, Snackbar.LENGTH_SHORT, ).show() From b3f345f672e5ea7cb14f9c86a8846e4802881ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manu=20Mu=C3=B1oz?= Date: Wed, 22 Jul 2026 11:27:35 +0200 Subject: [PATCH 04/23] fix: [ANDROAPP-7706] tei list attributes (#4980) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Manu Muñoz --- .../usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt index c5d520c6d0b..a609fedba08 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt @@ -126,7 +126,7 @@ class TEICardMapper( private fun getAdditionalInfoList(searchTEIModel: SearchTeiModel): List { val attributeList = - searchTEIModel.tei.attributeValues + searchTEIModel.attributeValues.values .map { AdditionalInfoItem( key = it.displayName, @@ -275,7 +275,7 @@ class TEICardMapper( ) { val programNames = enrolledPrograms?.map { it.displayName } - programNames?.let { + programNames?.takeIf { it.size > 1 }?.let { list.add( AdditionalInfoItem( key = resourceManager.getString(R.string.programs), From 5fba549b1c59a895c62fe81cacd3be54047834b8 Mon Sep 17 00:00:00 2001 From: andresmr Date: Wed, 22 Jul 2026 17:34:10 +0200 Subject: [PATCH 05/23] chore(tooling): add update-translations Transifex sync skill Add a self-contained Claude Code skill that syncs Transifex translations for both develop and main via the Transifex REST API (no tx CLI, no ~/.transifexrc). For each branch it spins up an isolated git worktree and: - pushes the branch's sources to its own resources (develop--* / main--*), verifying no source key is silently missing on the server; - pulls translations for every language that has at least one translated key; - applies the KMP escaped-question-mark fix (\? -> ?) that the Compose resource parser requires and that Transifex cannot fix server-side; - removes empty stub files and restores any in-source translation the server is missing so no real translation is lost; - opens a pull request into the branch. The API token is read from local.properties (TX_TOKEN), falling back to $TX_TOKEN. The helper is config-driven from .tx/config, so it stays correct as resources are added or removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/update-translations/SKILL.md | 176 +++++ .../scripts/transifex_sync.py | 621 ++++++++++++++++++ 2 files changed, 797 insertions(+) create mode 100644 .claude/skills/update-translations/SKILL.md create mode 100755 .claude/skills/update-translations/scripts/transifex_sync.py diff --git a/.claude/skills/update-translations/SKILL.md b/.claude/skills/update-translations/SKILL.md new file mode 100644 index 00000000000..a9c8a7efd0a --- /dev/null +++ b/.claude/skills/update-translations/SKILL.md @@ -0,0 +1,176 @@ +--- +name: update-translations +description: > + Sync Transifex translations for the DHIS2 Android Capture App on BOTH the + develop and main branches in parallel, talking directly to the Transifex REST + API (no tx CLI). For each branch it spins up an isolated git worktree, pushes + the branch's sources to its own Transifex resources (develop--* / main--*), + pulls the translations for every language that has any key translated, applies + the KMP escaped-character fix, cleans up empty stubs, and opens a pull request. + Invoke for "update translations", "sync Transifex", "pull the latest + translations", or "push sources to Transifex". +--- + +# Update Translations (Transifex sync) + +Push sources and pull translations for **both** `develop` and `main`, each in its +own worktree, ending in a PR per branch. The two branches map to **separate** +Transifex resources — a `--branch develop` push only touches `develop--*`, a +`--branch main` push only touches `main--*` — so both runs are safe in parallel. + +Everything goes through one config-driven helper that talks to the Transifex v3 +REST API directly (no `tx` CLI, no `~/.transifexrc`): + +``` +.claude/skills/update-translations/scripts/transifex_sync.py \ + --branch +``` + +It parses `.tx/config` for the resource list, source files, `file_filter` +patterns, `lang_map` and `source_lang` — nothing about the modules is hardcoded, +so it stays correct as resources are added or removed. + +## Prerequisites (check first; stop and tell the user if any is missing) + +- **Python 3** (standard library only — no pip installs). +- **Transifex API token** in `local.properties` as `TX_TOKEN=...` (already there + alongside `SONAR_TOKEN` etc.). `local.properties` is gitignored, so it exists + only in the primary checkout — see the token step below for worktrees. The + helper also accepts `$TX_TOKEN` from the environment. +- **gh CLI** authenticated (`gh auth status`) for opening PRs. +- A clean primary working tree is NOT required — work happens in throwaway + worktrees on fresh branches, never disturbing the user's checkout. + +## Orchestration (you, the invoking agent) + +1. Resolve the stable paths, the token, and a datestamp up front (scripts can't + read the clock; you pass values in): + ```bash + REPO="$(git rev-parse --show-toplevel)" + SCRIPT="$REPO/.claude/skills/update-translations/scripts/transifex_sync.py" + DATE="$(date +%Y%m%d)" + git -C "$REPO" fetch origin + ``` +2. Create one worktree per branch on a fresh sync branch off the **remote** tip + (basing on `origin/` avoids the "branch already checked out" error and + guarantees the newest sources): + ```bash + git -C "$REPO" worktree add -b "develop-transifex-$DATE" "$REPO/../tx-sync-develop-$DATE" origin/develop + git -C "$REPO" worktree add -b "main-transifex-$DATE" "$REPO/../tx-sync-main-$DATE" origin/main + ``` + > The `develop-transifex-*` name intentionally matches the existing + > `fix-kmp-translations.yml` action, which then runs on that PR as a CI safety + > net. `main` has no such action, which is exactly why this skill applies the + > escaping fix itself for both branches. +3. Spawn **two `general-purpose` agents in parallel** (both Agent calls in one + message), one per branch. Give each: `$REPO` (so it can read the token — the + worktrees have no `local.properties`), its worktree path, its branch name, its + sync-branch name, and `$SCRIPT`. Pass the per-branch procedure below verbatim. +4. When both return, report both PR URLs and each `postpull` summary. Then remove + the worktrees (branches already live on origin): + ```bash + git -C "$REPO" worktree remove --force "$REPO/../tx-sync-develop-$DATE" + git -C "$REPO" worktree remove --force "$REPO/../tx-sync-main-$DATE" + ``` + +## Per-branch procedure (each worktree agent runs this from its worktree) + +Substitute `` (`develop`/`main`), `` +(`-transifex-`), `` (worktree path), `` (primary repo), +`