diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6eac2a5e..5a4281eb 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,11 +21,43 @@ concurrency: cancel-in-progress: true jobs: + # ───────────────────────────────────────────────────────────────────── + # Job 0: Detect changes — skips heavy Android build/tests for doc-only PRs + # ───────────────────────────────────────────────────────────────────── + changes: + name: Detect Changes + runs-on: ubuntu-latest + timeout-minutes: 3 + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Paths Filter + uses: dorny/paths-filter@v4 + id: filter + with: + filters: | + code: + - 'app/**' + - 'gradle/**' + - 'build.gradle.kts' + - 'settings.gradle.kts' + - 'gradle.properties' + - 'gradlew' + - 'gradlew.bat' + - '.github/workflows/android.yml' + - '.editorconfig' + - 'flake.nix' + - 'flake.lock' + # ───────────────────────────────────────────────────────────────────── # Job 1: ktlint (code style) — fastest feedback, runs in parallel. # ───────────────────────────────────────────────────────────────────── ktlint: name: ktlint — Code Style + needs: [changes] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -52,13 +84,13 @@ jobs: uses: actions/cache@v6 with: path: ~/.local/bin/ktlint - key: ktlint-1.2.1 + key: ktlint-1.8.0 - name: Install ktlint if: steps.cache-ktlint.outputs.cache-hit != 'true' run: | mkdir -p ~/.local/bin - curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.2.1/ktlint + curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.8.0/ktlint chmod +x ktlint mv ktlint ~/.local/bin/ @@ -88,6 +120,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── android-lint: name: Android Lint — Static Analysis + needs: [changes] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -125,6 +159,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── unit-tests: name: Unit & Integration Tests + needs: [changes] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -162,7 +198,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── build: name: Build Debug APK - needs: [ktlint, android-lint, unit-tests] + needs: [changes, ktlint, android-lint, unit-tests] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -204,7 +241,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── release-compile: name: Release Compile Check - needs: [ktlint, android-lint, unit-tests] + needs: [changes, ktlint, android-lint, unit-tests] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -236,6 +274,8 @@ jobs: # ───────────────────────────────────────────────────────────────────── instrumented-tests: name: Instrumented Tests + needs: [changes] + if: github.event_name != 'pull_request' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -299,7 +339,7 @@ jobs: # ───────────────────────────────────────────────────────────────────── ci-summary: name: CI Summary - needs: [ktlint, android-lint, unit-tests, build, release-compile] + needs: [changes, ktlint, android-lint, unit-tests, build, release-compile] runs-on: ubuntu-latest timeout-minutes: 2 if: always() @@ -323,7 +363,7 @@ jobs: name="${entry%%:*}" result="${entry#*:}" echo "::notice::$name = $result" - if [[ "$result" != "success" ]]; then + if [[ "$result" != "success" && "$result" != "skipped" ]]; then FAILED=1 FAILED_JOBS+="$name " fi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fd4acdd0..f1c19e7b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,8 +3,34 @@ name: "CodeQL Analysis" on: push: branches: [ "main", "dev" ] + paths-ignore: + - '**.md' + - 'docs/**' + - '.github/PULL_REQUEST_TEMPLATE.md' + - '.github/workflows/pr-labeler.yml' + - '.github/workflows/stale.yml' + - '.github/workflows/merge-conflict-detector.yml' + - 'LICENSE' + - 'fastlane/**' + - '.gitignore' + - '.editorconfig' + - '.envrc' + - '*.html' pull_request: branches: [ "main", "dev" ] + paths-ignore: + - '**.md' + - 'docs/**' + - '.github/PULL_REQUEST_TEMPLATE.md' + - '.github/workflows/pr-labeler.yml' + - '.github/workflows/stale.yml' + - '.github/workflows/merge-conflict-detector.yml' + - 'LICENSE' + - 'fastlane/**' + - '.gitignore' + - '.editorconfig' + - '.envrc' + - '*.html' schedule: - cron: '30 3 * * 1' # Runs at 03:30 UTC every Monday diff --git a/.weblate.json b/.weblate.json new file mode 100644 index 00000000..39ddb919 --- /dev/null +++ b/.weblate.json @@ -0,0 +1,14 @@ +[ + { + "name": "Hermes Mobile", + "slug": "hermes-mobile", + "project": "hermes-mobile", + "vcs": "git", + "branch": "dev", + "filemask": "app/src/main/res/values-*/strings.xml", + "template": "app/src/main/res/values/strings.xml", + "file_format": "android", + "new_base": "app/src/main/res/values/strings.xml", + "language_code_style": "android" + } +] diff --git a/AGENTS.md b/AGENTS.md index ec463978..254d6894 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ If you have a local Android SDK (`ANDROID_HOME` set), these work: ```bash # Download the matching binary -curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.2.1/ktlint +curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.8.0/ktlint chmod +x ktlint ./ktlint # check one file ./ktlint --format # auto-fix @@ -48,7 +48,7 @@ chmod +x ktlint | Job | Purpose | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `ktlint` | ktlint 1.2.1 style check + `checkColorLiterals` (hardcoded Color guard, issue #622) | +| `ktlint` | ktlint 1.8.0 style check + `checkColorLiterals` (hardcoded Color guard, issue #622) | | `android-lint` | Android Lint | | `unit-tests` | JUnit | | `build` | assembleDebug (gated by the 3 above) | @@ -68,7 +68,7 @@ Requires `permissions: contents: write` on the `build-release` job. ## Code Style -- **ktlint 1.2.1** is enforced in CI. Run `./ktlint --format` before pushing. +- **ktlint 1.8.0** is enforced in CI. Run `./ktlint --format` before pushing. - Import ordering is the #1 CI failure: ktlint enforces ASCII-lexicographic order (uppercase before lowercase: `LaunchedEffect` before `collectAsState`). - `const val` must use SCREAMING_SNAKE_CASE. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a84dee..9bf2da36 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,13 +67,23 @@ If you use AI coding tools (including agents) to contribute: --- +## Translations + +Translations for Hermes Mobile are managed through [Hosted Weblate](https://hosted.weblate.org/projects/hermes-mobile/hermes-mobile/). + +- Source strings are located in `app/src/main/res/values/strings.xml`. +- Localized strings are stored in `app/src/main/res/values-/strings.xml`. +- You can contribute translations directly on Weblate without modifying code or opening manual PRs. + +--- + ## Code Style We enforce Kotlin coding conventions and Jetpack Compose best practices. ### Kotlin Formatting -- Code formatting is checked and enforced by **ktlint 1.2.1** in CI. The authoritative command is the Gradle task (not the standalone binary): +- Code formatting is checked and enforced by **ktlint 1.8.0** in CI. The authoritative command is the Gradle task (not the standalone binary): ```bash ./gradlew ktlintCheck # check (CI-equivalent) ./gradlew ktlintFormat # auto-fix, then re-check with ktlintCheck diff --git a/README.md b/README.md index 64fa7aaf..393f0875 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ app/src/main/java/com/m57/hermescontrol/ - **Theming:** 6 built-in presets (Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Neon Noir) + Material You dynamic colors - **Image Loading:** Coil 2.7.0 - **Testing:** JUnit 5, MockK, Turbine, Espresso, Compose UI testing -- **Formatting:** `ktlint` 1.2.1 style rules (checked automatically in CI) +- **Formatting:** `ktlint` 1.8.0 style rules (checked automatically in CI) --- @@ -159,6 +159,10 @@ app/src/main/java/com/m57/hermescontrol/ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for our branch workflow, code style guidelines, and PR checklist. +### Translations + +Help translate Hermes Mobile into your language on [Hosted Weblate](https://hosted.weblate.org/projects/hermes-mobile/hermes-mobile/)! + For developer-specific details, code conventions, and project architecture notes, refer to [AGENTS.md](AGENTS.md). --- diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fe12882..8da078e3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,12 @@ + + + + + +