From 5a2016015d321b530bd76f20bb0bf82f2ededf20 Mon Sep 17 00:00:00 2001 From: stslex Date: Mon, 22 Dec 2025 23:57:57 +0300 Subject: [PATCH 1/5] feat: implement snackbar functionality for user notifications refactor: create SnackbarManager and AppSnackbarModel for better state management Summary: This commit introduces a snackbar system to display user notifications within the app. It includes the creation of `SnackbarManager` to manage snackbar messages and `AppSnackbarModel` to define the structure of snackbar data. Key changes: - **Snackbar Management:** - Added `SnackbarManager` to handle snackbar messages using a `MutableSharedFlow`. - Introduced `AppSnackbarModel` to encapsulate snackbar properties such as message, action label, and action callback. - **UI Integration:** - Integrated snackbar functionality into the main app UI, allowing for dynamic display of notifications based on user actions. - Implemented `AppSnackBar` composable to render the snackbar at the bottom of the screen. --- .../java/io/github/stslex/workeeper/App.kt | 32 +++++++++++++++++++ .../core/ui/kit/snackbar/AppSnackbarModel.kt | 11 +++++++ .../core/ui/kit/snackbar/SnackbarManager.kt | 29 +++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/AppSnackbarModel.kt create mode 100644 core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/SnackbarManager.kt diff --git a/app/app/src/main/java/io/github/stslex/workeeper/App.kt b/app/app/src/main/java/io/github/stslex/workeeper/App.kt index 060903d..6ffb89a 100644 --- a/app/app/src/main/java/io/github/stslex/workeeper/App.kt +++ b/app/app/src/main/java/io/github/stslex/workeeper/App.kt @@ -12,8 +12,12 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult.ActionPerformed +import androidx.compose.material3.SnackbarResult.Dismissed import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,6 +25,8 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.zIndex import io.github.stslex.workeeper.bottom_app_bar.WorkeeperBottomAppBar +import io.github.stslex.workeeper.core.ui.kit.components.snackbar.AppSnackBar +import io.github.stslex.workeeper.core.ui.kit.snackbar.SnackbarManager import io.github.stslex.workeeper.core.ui.kit.theme.AppTheme import io.github.stslex.workeeper.core.ui.kit.theme.AppUi import io.github.stslex.workeeper.core.ui.navigation.LocalNavigator @@ -40,6 +46,24 @@ fun App() { LocalNavigator provides navigator, LocalRootComponent provides rootComponent, ) { + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(Unit) { + SnackbarManager.snackbar + .collect { model -> + val result = snackbarHostState.showSnackbar( + message = model.message, + actionLabel = model.actionLabel, + withDismissAction = model.withDismissAction, + ) + when (result) { + ActionPerformed -> model.action() + + Dismissed -> Unit // No-op + } + } + } + Box( modifier = Modifier .fillMaxSize() @@ -79,6 +103,14 @@ fun App() { modifier = Modifier, navigator = navigator, ) + + AppSnackBar( + modifier = Modifier.align(Alignment.BottomCenter), + state = snackbarHostState, + onActionClick = { + snackbarHostState.currentSnackbarData?.performAction() + }, + ) } } } diff --git a/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/AppSnackbarModel.kt b/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/AppSnackbarModel.kt new file mode 100644 index 0000000..cdbf7cf --- /dev/null +++ b/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/AppSnackbarModel.kt @@ -0,0 +1,11 @@ +package io.github.stslex.workeeper.core.ui.kit.snackbar + +import androidx.compose.runtime.Stable + +@Stable +data class AppSnackbarModel( + val message: String, + val actionLabel: String? = null, + val withDismissAction: Boolean = false, + val action: () -> Unit = { }, +) diff --git a/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/SnackbarManager.kt b/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/SnackbarManager.kt new file mode 100644 index 0000000..7208291 --- /dev/null +++ b/core/ui/kit/src/main/kotlin/io/github/stslex/workeeper/core/ui/kit/snackbar/SnackbarManager.kt @@ -0,0 +1,29 @@ +package io.github.stslex.workeeper.core.ui.kit.snackbar + +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +object SnackbarManager { + + private val _snackbar: MutableSharedFlow = MutableSharedFlow() + val snackbar: SharedFlow = _snackbar.asSharedFlow() + + fun showSnackbar(model: AppSnackbarModel) { + _snackbar.tryEmit(model) + } + + fun showSnackbar( + message: String, + actionLabel: String? = null, + withDismissAction: Boolean = false, + action: () -> Unit = {}, + ): Unit = showSnackbar( + AppSnackbarModel( + message = message, + actionLabel = actionLabel, + withDismissAction = withDismissAction, + action = action, + ), + ) +} From b91a654f85f0d89eda778ba072aaa09b9754c5d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 16:21:40 +0000 Subject: [PATCH 2/5] disable pre-commit hook and move UI tests to optional workflow - Add early exit to .githooks/pre-commit to disable linting checks without deleting the hook - Remove smoke-tests, regression-tests, and test-summary jobs from android_build_unified.yml; also remove test_suite workflow_dispatch input and the build artifact upload step that only served UI tests - Add .github/workflows/ui_tests.yml with manual-only (workflow_dispatch) trigger for smoke and regression UI tests; UI tests are no longer required for PRs or merges https://claude.ai/code/session_01A2nLtngsixsDMJrHY1X7Ff --- .githooks/pre-commit | 2 + .github/workflows/android_build_unified.yml | 424 -------------------- .github/workflows/ui_tests.yml | 373 +++++++++++++++++ 3 files changed, 375 insertions(+), 424 deletions(-) create mode 100644 .github/workflows/ui_tests.yml diff --git a/.githooks/pre-commit b/.githooks/pre-commit index e46ec46..05fc424 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,6 +1,8 @@ #!/bin/sh # Pre-commit hook to run linting +# DISABLED: exit early without running checks +exit 0 echo "Running pre-commit checks..." diff --git a/.github/workflows/android_build_unified.yml b/.github/workflows/android_build_unified.yml index 78afef2..7bf48b0 100644 --- a/.github/workflows/android_build_unified.yml +++ b/.github/workflows/android_build_unified.yml @@ -5,17 +5,6 @@ on: branches: [ master ] pull_request: workflow_dispatch: - inputs: - test_suite: - description: 'Test suite to run' - required: true - default: 'smoke' - type: choice - options: - - smoke - - regression - - all - - none permissions: contents: read @@ -165,416 +154,3 @@ jobs: uses: yutailang0119/action-android-lint@v4 with: report-path: "**/build/reports/lint-results-*.xml" - - # Cache build outputs for UI tests - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: build-outputs - path: | - **/build/outputs/apk/ - **/build/outputs/androidTest-results/ - retention-days: 1 - - # Smoke UI tests - run on all PRs and branches - smoke-tests: - name: Smoke UI Tests - runs-on: ubuntu-latest - needs: build - timeout-minutes: 60 - if: | - github.event_name == 'workflow_dispatch' && inputs.test_suite == 'smoke' || - github.event_name == 'workflow_dispatch' && inputs.test_suite == 'all' || - github.event_name == 'pull_request' || - github.event_name == 'push' - - strategy: - matrix: - # Default to API 34 for speed; expand to 34 for PRs into master or all/regression suites - # Note: API 33,28 removed due to unreliable emulator boot issues on GitHub Actions - api-level: [ 34 ] - target: [ google_apis ] - arch: [ x86_64 ] - fail-fast: false - - steps: - - name: Checkout branch - uses: actions/checkout@v4 - - - name: Enable KVM group perms - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: gradle - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Configure Keystore - env: - KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - run: | - echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc - gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks - echo "storeFile=keystore.jks" >> keystore.properties - echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties - echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties - echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties - - - name: Create Google Services Config files - env: - GOOGLE_SERVICES_JSON_STORE: ${{ secrets.GOOGLE_SERVICES_JSON_STORE }} - GOOGLE_SERVICES_JSON_DEV: ${{ secrets.GOOGLE_SERVICES_JSON_DEV }} - run: | - echo "$GOOGLE_SERVICES_JSON_STORE" > app/store/google-services.json.b64 - base64 -d -i app/store/google-services.json.b64 > app/store/google-services.json - echo "$GOOGLE_SERVICES_JSON_DEV" > app/dev/google-services.json.b64 - base64 -d -i app/dev/google-services.json.b64 > app/dev/google-services.json - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Use CI-optimized Gradle properties - run: cp .github/properties/gradle-ci.properties gradle.properties - - - name: Use CI-optimized Convention Gradle properties - run: cp .github/properties/gradle-convention-ci.properties build-logic/gradle.properties - - - name: Restore Gradle build cache - id: smoke-gradle-build-cache - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches/build-cache-* - key: gradle-build-cache-${{ runner.os }}-${{ hashFiles('settings.gradle.kts', '**/build.gradle.kts', 'gradle/libs.versions.toml', 'gradle.properties') }} - restore-keys: | - gradle-build-cache-${{ runner.os }}- - - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - arch: ${{ matrix.arch }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 - disable-animations: true - emulator-boot-timeout: 900 - script: echo "Generated AVD snapshot for caching." - - - name: Run Smoke UI tests on emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - arch: ${{ matrix.arch }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 - disable-animations: true - emulator-boot-timeout: 900 - script: | - adb logcat > logcat-smoke.txt & - ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=io.github.stslex.workeeper.core.ui.test.annotations.Smoke --full-stacktrace --continue - adb logcat -d > logcat-smoke-final.txt - - - name: Publish Smoke Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: | - **/build/outputs/androidTest-results/connected/**/*.xml - **/build/test-results/**/*.xml - check_name: Smoke UI Test Results (API ${{ matrix.api-level }}) - comment_title: Smoke UI Test Results (API ${{ matrix.api-level }}) - commit: ${{ github.event.pull_request.head.sha || github.sha }} - report_individual_runs: true - deduplicate_classes_by_file_name: false - compare_to_earlier_commit: true - pull_request_build: commit - check_run_annotations: all tests, skipped tests - - - name: Detailed Smoke Test Report - uses: mikepenz/action-junit-report@v4 - if: always() - with: - report_paths: | - **/build/outputs/androidTest-results/connected/**/*.xml - **/build/test-results/**/*.xml - check_name: 📊 Detailed Smoke Test Report (API ${{ matrix.api-level }}) - detailed_summary: true - include_passed: true - fail_on_failure: false - require_tests: true - annotate_only: false - job_summary: true - - - name: Upload smoke test reports - uses: actions/upload-artifact@v4 - if: always() - with: - name: smoke-test-reports-api-${{ matrix.api-level }} - path: | - **/build/reports/androidTests/ - **/build/outputs/androidTest-results/ - retention-days: 30 - - - name: Upload logcat output - uses: actions/upload-artifact@v4 - if: always() - with: - name: logcat-smoke-api-${{ matrix.api-level }} - path: | - logcat-smoke.txt - logcat-smoke-final.txt - retention-days: 7 - - - name: Upload screenshots on failure - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots-smoke-api-${{ matrix.api-level }} - path: | - **/build/outputs/connected_android_test_additional_output/ - retention-days: 14 - if-no-files-found: ignore - - # Regression UI tests - run only for master branch PRs and pushes - regression-tests: - name: Regression UI Tests - runs-on: ubuntu-latest - needs: build - timeout-minutes: 120 - if: | - github.event_name == 'workflow_dispatch' && (inputs.test_suite == 'regression' || inputs.test_suite == 'all') || - (github.event_name == 'pull_request' && github.base_ref == 'master') || - (github.event_name == 'push' && github.ref == 'refs/heads/master') - - strategy: - matrix: - # Regression tests always run on all API levels (comprehensive coverage) - # Note: API 33, 28 removed due to unreliable emulator boot issues on GitHub Actions - api-level: [ 34 ] - target: [ google_apis ] - arch: [ x86_64 ] - fail-fast: false - - steps: - - name: Checkout branch - uses: actions/checkout@v4 - - - name: Enable KVM group perms - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: gradle - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Configure Keystore - env: - KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} - KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} - KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} - run: | - echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc - gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks - echo "storeFile=keystore.jks" >> keystore.properties - echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties - echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties - echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties - - - name: Create Google Services Config files - env: - GOOGLE_SERVICES_JSON_STORE: ${{ secrets.GOOGLE_SERVICES_JSON_STORE }} - GOOGLE_SERVICES_JSON_DEV: ${{ secrets.GOOGLE_SERVICES_JSON_DEV }} - run: | - echo "$GOOGLE_SERVICES_JSON_STORE" > app/store/google-services.json.b64 - base64 -d -i app/store/google-services.json.b64 > app/store/google-services.json - echo "$GOOGLE_SERVICES_JSON_DEV" > app/dev/google-services.json.b64 - base64 -d -i app/dev/google-services.json.b64 > app/dev/google-services.json - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Use CI-optimized Gradle properties - run: cp .github/properties/gradle-ci.properties gradle.properties - - - name: Use CI-optimized Convention Gradle properties - run: cp .github/properties/gradle-convention-ci.properties build-logic/gradle.properties - - - name: Restore Gradle build cache - id: regression-gradle-build-cache - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches/build-cache-* - key: gradle-build-cache-${{ runner.os }}-${{ hashFiles('settings.gradle.kts', '**/build.gradle.kts', 'gradle/libs.versions.toml', 'gradle.properties') }} - restore-keys: | - gradle-build-cache-${{ runner.os }}- - - - name: AVD cache - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - arch: ${{ matrix.arch }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 - disable-animations: true - emulator-boot-timeout: 900 - script: echo "Generated AVD snapshot for caching." - - - name: Run Regression UI tests on emulator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - target: ${{ matrix.target }} - arch: ${{ matrix.arch }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 - disable-animations: true - emulator-boot-timeout: 900 - script: | - adb logcat > logcat-regression.txt & - ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=io.github.stslex.workeeper.core.ui.test.annotations.Regression --full-stacktrace --continue - adb logcat -d > logcat-regression-final.txt - - - name: Publish Regression Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() - with: - files: | - **/build/outputs/androidTest-results/connected/**/*.xml - **/build/test-results/**/*.xml - check_name: Regression UI Test Results (API ${{ matrix.api-level }}) - comment_title: Regression UI Test Results (API ${{ matrix.api-level }}) - commit: ${{ github.event.pull_request.head.sha || github.sha }} - report_individual_runs: true - deduplicate_classes_by_file_name: false - compare_to_earlier_commit: true - pull_request_build: commit - check_run_annotations: all tests, skipped tests - - - name: Detailed Regression Test Report - uses: mikepenz/action-junit-report@v4 - if: always() - with: - report_paths: | - **/build/outputs/androidTest-results/connected/**/*.xml - **/build/test-results/**/*.xml - check_name: 📊 Detailed Regression Test Report (API ${{ matrix.api-level }}) - detailed_summary: true - include_passed: true - fail_on_failure: false - require_tests: true - annotate_only: false - job_summary: true - - - name: Upload regression test reports - uses: actions/upload-artifact@v4 - if: always() - with: - name: regression-test-reports-api-${{ matrix.api-level }} - path: | - **/build/reports/androidTests/ - **/build/outputs/androidTest-results/ - retention-days: 30 - - - name: Upload logcat output - uses: actions/upload-artifact@v4 - if: always() - with: - name: logcat-regression-api-${{ matrix.api-level }} - path: | - logcat-regression.txt - logcat-regression-final.txt - retention-days: 7 - - - name: Upload screenshots on failure - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots-regression-api-${{ matrix.api-level }} - path: | - **/build/outputs/connected_android_test_additional_output/ - retention-days: 14 - if-no-files-found: ignore - - # Summary job - test-summary: - name: Test Summary - runs-on: ubuntu-latest - needs: [ smoke-tests, regression-tests ] - if: always() - - steps: - - name: Check test results - run: | - SMOKE_RESULT="${{ needs.smoke-tests.result }}" - REGRESSION_RESULT="${{ needs.regression-tests.result }}" - - echo "Smoke Tests: $SMOKE_RESULT" - echo "Regression Tests: $REGRESSION_RESULT" - - # Check for failures - if [[ "$SMOKE_RESULT" == "failure" ]] || [[ "$REGRESSION_RESULT" == "failure" ]]; then - echo "❌ Some tests failed" - exit 1 - fi - - # Check for cancellations - if [[ "$SMOKE_RESULT" == "cancelled" ]] || [[ "$REGRESSION_RESULT" == "cancelled" ]]; then - echo "⚠️ Some tests were cancelled" - exit 1 - fi - - # All tests passed or skipped (skipped is OK for regression on feature branches) - if [[ "$SMOKE_RESULT" == "success" ]]; then - if [[ "$REGRESSION_RESULT" == "success" ]]; then - echo "✅ All tests passed (Smoke + Regression)" - elif [[ "$REGRESSION_RESULT" == "skipped" ]]; then - echo "✅ Smoke tests passed (Regression tests skipped - not targeting master)" - else - echo "✅ Smoke tests passed" - fi - else - echo "⚠️ Unexpected test state" - exit 1 - fi diff --git a/.github/workflows/ui_tests.yml b/.github/workflows/ui_tests.yml new file mode 100644 index 0000000..3c1bee2 --- /dev/null +++ b/.github/workflows/ui_tests.yml @@ -0,0 +1,373 @@ +name: Android UI Tests (Optional) + +# UI tests are NOT required for PRs or merges. +# Run manually via workflow_dispatch when needed. +on: + workflow_dispatch: + inputs: + test_suite: + description: 'Test suite to run' + required: true + default: 'smoke' + type: choice + options: + - smoke + - regression + - all + +permissions: + contents: read + issues: read + checks: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Smoke UI tests - fast, critical tests with mocked data + smoke-tests: + name: Smoke UI Tests + runs-on: ubuntu-latest + timeout-minutes: 60 + if: inputs.test_suite == 'smoke' || inputs.test_suite == 'all' + + strategy: + matrix: + api-level: [ 34 ] + target: [ google_apis ] + arch: [ x86_64 ] + fail-fast: false + + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Configure Keystore + env: + KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} + KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} + KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} + run: | + echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc + gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks + echo "storeFile=keystore.jks" >> keystore.properties + echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties + echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties + echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties + + - name: Create Google Services Config files + env: + GOOGLE_SERVICES_JSON_STORE: ${{ secrets.GOOGLE_SERVICES_JSON_STORE }} + GOOGLE_SERVICES_JSON_DEV: ${{ secrets.GOOGLE_SERVICES_JSON_DEV }} + run: | + echo "$GOOGLE_SERVICES_JSON_STORE" > app/store/google-services.json.b64 + base64 -d -i app/store/google-services.json.b64 > app/store/google-services.json + echo "$GOOGLE_SERVICES_JSON_DEV" > app/dev/google-services.json.b64 + base64 -d -i app/dev/google-services.json.b64 > app/dev/google-services.json + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Use CI-optimized Gradle properties + run: cp .github/properties/gradle-ci.properties gradle.properties + + - name: Use CI-optimized Convention Gradle properties + run: cp .github/properties/gradle-convention-ci.properties build-logic/gradle.properties + + - name: Restore Gradle build cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/build-cache-* + key: gradle-build-cache-${{ runner.os }}-${{ hashFiles('settings.gradle.kts', '**/build.gradle.kts', 'gradle/libs.versions.toml', 'gradle.properties') }} + restore-keys: | + gradle-build-cache-${{ runner.os }}- + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }} + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 + disable-animations: true + emulator-boot-timeout: 900 + script: echo "Generated AVD snapshot for caching." + + - name: Run Smoke UI tests on emulator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 + disable-animations: true + emulator-boot-timeout: 900 + script: | + adb logcat > logcat-smoke.txt & + ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=io.github.stslex.workeeper.core.ui.test.annotations.Smoke --full-stacktrace --continue + adb logcat -d > logcat-smoke-final.txt + + - name: Publish Smoke Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + **/build/outputs/androidTest-results/connected/**/*.xml + **/build/test-results/**/*.xml + check_name: Smoke UI Test Results (API ${{ matrix.api-level }}) + comment_title: Smoke UI Test Results (API ${{ matrix.api-level }}) + commit: ${{ github.sha }} + report_individual_runs: true + deduplicate_classes_by_file_name: false + compare_to_earlier_commit: true + pull_request_build: commit + check_run_annotations: all tests, skipped tests + + - name: Detailed Smoke Test Report + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: | + **/build/outputs/androidTest-results/connected/**/*.xml + **/build/test-results/**/*.xml + check_name: Detailed Smoke Test Report (API ${{ matrix.api-level }}) + detailed_summary: true + include_passed: true + fail_on_failure: false + require_tests: true + annotate_only: false + job_summary: true + + - name: Upload smoke test reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: smoke-test-reports-api-${{ matrix.api-level }} + path: | + **/build/reports/androidTests/ + **/build/outputs/androidTest-results/ + retention-days: 30 + + - name: Upload logcat output + uses: actions/upload-artifact@v4 + if: always() + with: + name: logcat-smoke-api-${{ matrix.api-level }} + path: | + logcat-smoke.txt + logcat-smoke-final.txt + retention-days: 7 + + - name: Upload screenshots on failure + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots-smoke-api-${{ matrix.api-level }} + path: | + **/build/outputs/connected_android_test_additional_output/ + retention-days: 14 + if-no-files-found: ignore + + # Regression UI tests - comprehensive integration tests + regression-tests: + name: Regression UI Tests + runs-on: ubuntu-latest + timeout-minutes: 120 + if: inputs.test_suite == 'regression' || inputs.test_suite == 'all' + + strategy: + matrix: + api-level: [ 34 ] + target: [ google_apis ] + arch: [ x86_64 ] + fail-fast: false + + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Configure Keystore + env: + KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} + KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} + KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} + run: | + echo "${{ secrets.KEYSTORE }}" > keystore.jks.asc + gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks + echo "storeFile=keystore.jks" >> keystore.properties + echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> keystore.properties + echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> keystore.properties + echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> keystore.properties + + - name: Create Google Services Config files + env: + GOOGLE_SERVICES_JSON_STORE: ${{ secrets.GOOGLE_SERVICES_JSON_STORE }} + GOOGLE_SERVICES_JSON_DEV: ${{ secrets.GOOGLE_SERVICES_JSON_DEV }} + run: | + echo "$GOOGLE_SERVICES_JSON_STORE" > app/store/google-services.json.b64 + base64 -d -i app/store/google-services.json.b64 > app/store/google-services.json + echo "$GOOGLE_SERVICES_JSON_DEV" > app/dev/google-services.json.b64 + base64 -d -i app/dev/google-services.json.b64 > app/dev/google-services.json + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Use CI-optimized Gradle properties + run: cp .github/properties/gradle-ci.properties gradle.properties + + - name: Use CI-optimized Convention Gradle properties + run: cp .github/properties/gradle-convention-ci.properties build-logic/gradle.properties + + - name: Restore Gradle build cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/build-cache-* + key: gradle-build-cache-${{ runner.os }}-${{ hashFiles('settings.gradle.kts', '**/build.gradle.kts', 'gradle/libs.versions.toml', 'gradle.properties') }} + restore-keys: | + gradle-build-cache-${{ runner.os }}- + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }}-${{ matrix.target }}-${{ matrix.arch }} + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 + disable-animations: true + emulator-boot-timeout: 900 + script: echo "Generated AVD snapshot for caching." + + - name: Run Regression UI tests on emulator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.target }} + arch: ${{ matrix.arch }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096 -partition-size 4096 + disable-animations: true + emulator-boot-timeout: 900 + script: | + adb logcat > logcat-regression.txt & + ./gradlew connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=io.github.stslex.workeeper.core.ui.test.annotations.Regression --full-stacktrace --continue + adb logcat -d > logcat-regression-final.txt + + - name: Publish Regression Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + **/build/outputs/androidTest-results/connected/**/*.xml + **/build/test-results/**/*.xml + check_name: Regression UI Test Results (API ${{ matrix.api-level }}) + comment_title: Regression UI Test Results (API ${{ matrix.api-level }}) + commit: ${{ github.sha }} + report_individual_runs: true + deduplicate_classes_by_file_name: false + compare_to_earlier_commit: true + pull_request_build: commit + check_run_annotations: all tests, skipped tests + + - name: Detailed Regression Test Report + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: | + **/build/outputs/androidTest-results/connected/**/*.xml + **/build/test-results/**/*.xml + check_name: Detailed Regression Test Report (API ${{ matrix.api-level }}) + detailed_summary: true + include_passed: true + fail_on_failure: false + require_tests: true + annotate_only: false + job_summary: true + + - name: Upload regression test reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: regression-test-reports-api-${{ matrix.api-level }} + path: | + **/build/reports/androidTests/ + **/build/outputs/androidTest-results/ + retention-days: 30 + + - name: Upload logcat output + uses: actions/upload-artifact@v4 + if: always() + with: + name: logcat-regression-api-${{ matrix.api-level }} + path: | + logcat-regression.txt + logcat-regression-final.txt + retention-days: 7 + + - name: Upload screenshots on failure + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots-regression-api-${{ matrix.api-level }} + path: | + **/build/outputs/connected_android_test_additional_output/ + retention-days: 14 + if-no-files-found: ignore From 1d95ae19ebd48b95e43b6b3ec10c0f2fff20ca6b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 18:36:36 +0000 Subject: [PATCH 3/5] fix: update deprecated CI actions and fix junit BOM version - Upgrade actions/setup-java from @v3 to @v4 in android_build_unified.yml, android_deploy_beta.yml, android_deploy_prod.yml - Pin ad-m/github-push-action to @v0.8.0 (was @master) in android_deploy_beta.yml, android_deploy_prod.yml, version_updater.yml - Fix junit BOM version from non-existent 6.0.1 to 5.13.4 (matching junitJupiter); remove now-redundant junitJupiter entry https://claude.ai/code/session_01A2nLtngsixsDMJrHY1X7Ff --- .github/workflows/android_build_unified.yml | 2 +- .github/workflows/android_deploy_beta.yml | 4 ++-- .github/workflows/android_deploy_prod.yml | 4 ++-- .github/workflows/version_updater.yml | 2 +- gradle/libs.versions.toml | 3 +-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/android_build_unified.yml b/.github/workflows/android_build_unified.yml index 7bf48b0..3bfe73d 100644 --- a/.github/workflows/android_build_unified.yml +++ b/.github/workflows/android_build_unified.yml @@ -34,7 +34,7 @@ jobs: gpg -d --passphrase "${{ secrets.KEYSTORE_PASSPHRASE }}" --batch keystore.jks.asc > keystore.jks - name: Set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' diff --git a/.github/workflows/android_deploy_beta.yml b/.github/workflows/android_deploy_beta.yml index dd6bf07..fa1fee5 100644 --- a/.github/workflows/android_deploy_beta.yml +++ b/.github/workflows/android_deploy_beta.yml @@ -70,7 +70,7 @@ jobs: ${{ runner.os }}-gems- - name: set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' @@ -133,7 +133,7 @@ jobs: fi - name: Push changes - uses: ad-m/github-push-action@master + uses: ad-m/github-push-action@v0.8.0 with: github_token: ${{ secrets.PUSH_TOKEN }} branch: ${{ github.ref }} diff --git a/.github/workflows/android_deploy_prod.yml b/.github/workflows/android_deploy_prod.yml index a733fe4..add390c 100644 --- a/.github/workflows/android_deploy_prod.yml +++ b/.github/workflows/android_deploy_prod.yml @@ -66,7 +66,7 @@ jobs: ${{ runner.os }}-gems- - name: set up JDK 21 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' @@ -130,7 +130,7 @@ jobs: fi - name: Push changes - uses: ad-m/github-push-action@master + uses: ad-m/github-push-action@v0.8.0 with: github_token: ${{ secrets.PUSH_TOKEN }} branch: ${{ github.ref }} diff --git a/.github/workflows/version_updater.yml b/.github/workflows/version_updater.yml index 62a2331..6cb4537 100644 --- a/.github/workflows/version_updater.yml +++ b/.github/workflows/version_updater.yml @@ -77,7 +77,7 @@ jobs: fi - name: Push changes - uses: ad-m/github-push-action@master + uses: ad-m/github-push-action@v0.8.0 with: github_token: ${{ secrets.PUSH_TOKEN }} branch: ${{ github.ref }} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f81418b..b44fbfa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ hilt = "2.57.2" hiltCompose = "1.3.0" mockk = "1.14.7" -junit = "6.0.1" +junit = "5.13.4" androidxJunit = "1.3.0" espresso = "3.7.0" robolectric = "4.16" @@ -56,7 +56,6 @@ detektCompose = "0.5.3" # decompose dependencies essenty = "2.5.0" parcelize = "0.2.4" -junitJupiter = "5.13.4" junitKtx = "1.3.0" # Other tools From 567a499b85409e60aa767d3cd91c0c82b13d1c52 Mon Sep 17 00:00:00 2001 From: stslex Date: Mon, 23 Mar 2026 00:39:46 +0300 Subject: [PATCH 4/5] refactor: update Gradle properties and dependencies for compatibility This commit updates the Gradle wrapper to version 9.4.1 and modifies various Kotlin and Android plugin versions for improved compatibility. Additionally, it refactors the configuration of common extensions and applies lint settings more efficiently. --- .../AndroidLibraryComposeConventionPlugin.kt | 6 +-- .../kotlin/AndroidLibraryConventionPlugin.kt | 6 +-- .../src/main/kotlin/LintConventionPlugin.kt | 54 +++++++++---------- .../kotlin/RoomLibraryConventionPlugin.kt | 4 +- .../github/stslex/workeeper/ComposeAndroid.kt | 2 +- .../stslex/workeeper/ConfigureApplication.kt | 7 ++- .../github/stslex/workeeper/KotlinAndroid.kt | 12 ++--- build.gradle.kts | 8 +++ gradle/libs.versions.toml | 12 ++--- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 3 ++ 11 files changed, 58 insertions(+), 58 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt index b810579..8eedc00 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt @@ -1,7 +1,6 @@ import AppExt.findPluginId -import AppExt.findVersionInt import AppExt.libs -import com.android.build.gradle.LibraryExtension +import com.android.build.api.dsl.LibraryExtension import io.github.stslex.workeeper.configureAndroidCompose import io.github.stslex.workeeper.configureKotlinAndroid import org.gradle.api.Plugin @@ -14,7 +13,6 @@ class AndroidLibraryComposeConventionPlugin : Plugin { with(target) { pluginManager.apply { apply(libs.findPluginId("library")) - apply(libs.findPluginId("kotlin")) apply(libs.findPluginId("composeCompiler")) apply(libs.findPluginId("serialization")) apply(libs.findPluginId("ksp")) @@ -28,9 +26,7 @@ class AndroidLibraryComposeConventionPlugin : Plugin { configureAndroidCompose(this) defaultConfig.apply { - targetSdk = libs.findVersionInt("targetSdk") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") buildTypes { release { isMinifyEnabled = false diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 527499e..c4774e3 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -1,7 +1,6 @@ import AppExt.findPluginId -import AppExt.findVersionInt import AppExt.libs -import com.android.build.gradle.LibraryExtension +import com.android.build.api.dsl.LibraryExtension import io.github.stslex.workeeper.configureKotlinAndroid import org.gradle.api.Plugin import org.gradle.api.Project @@ -13,7 +12,6 @@ class AndroidLibraryConventionPlugin : Plugin { with(target) { pluginManager.apply { apply(libs.findPluginId("library")) - apply(libs.findPluginId("kotlin")) apply(libs.findPluginId("ksp")) apply(libs.findPluginId("convention.lint")) } @@ -21,9 +19,7 @@ class AndroidLibraryConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) defaultConfig.apply { - targetSdk = libs.findVersionInt("targetSdk") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") buildTypes { release { isMinifyEnabled = false diff --git a/build-logic/convention/src/main/kotlin/LintConventionPlugin.kt b/build-logic/convention/src/main/kotlin/LintConventionPlugin.kt index da6a2c7..417288c 100644 --- a/build-logic/convention/src/main/kotlin/LintConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/LintConventionPlugin.kt @@ -14,38 +14,36 @@ class LintConventionPlugin : Plugin { } val commonExtension = extensions.findByType(CommonExtension::class.java) - commonExtension?.apply { - lint { - // Main lint configuration (includes centralized suppressions) - lintConfig = rootProject.file("lint-rules/lint.xml") + commonExtension?.lint?.apply { + // Main lint configuration (includes centralized suppressions) + lintConfig = rootProject.file("lint-rules/lint.xml") - // Report configuration - htmlReport = true - xmlReport = true - sarifReport = true - textReport = false + // Report configuration + htmlReport = true + xmlReport = true + sarifReport = true + textReport = false - // Analysis configuration - checkDependencies = true - abortOnError = true - ignoreWarnings = false - checkAllWarnings = true - warningsAsErrors = true - checkGeneratedSources = false - explainIssues = true - noLines = false - quiet = false - checkReleaseBuilds = true - ignoreTestSources = true + // Analysis configuration + checkDependencies = true + abortOnError = true + ignoreWarnings = false + checkAllWarnings = true + warningsAsErrors = true + checkGeneratedSources = false + explainIssues = true + noLines = false + quiet = false + checkReleaseBuilds = true + ignoreTestSources = true - // Single centralized baseline file for all modules - baseline = rootProject.file("lint-rules/lint-baseline.xml") + // Single centralized baseline file for all modules + baseline = rootProject.file("lint-rules/lint-baseline.xml") - // Output directories - htmlOutput = file("build/reports/lint-results.html") - xmlOutput = file("build/reports/lint-results.xml") - sarifOutput = file("build/reports/lint-results.sarif") - } + // Output directories + htmlOutput = file("build/reports/lint-results.html") + xmlOutput = file("build/reports/lint-results.xml") + sarifOutput = file("build/reports/lint-results.sarif") } // Configure detekt for each module diff --git a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt index 97d9f6d..d630225 100644 --- a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt @@ -1,4 +1,4 @@ -import AppExt.androidTestApi +import AppExt.androidTestImplementation import AppExt.findPluginId import AppExt.implementation import AppExt.implementationBundle @@ -36,7 +36,7 @@ class RoomLibraryConventionPlugin : Plugin { ksp("androidx-room-compiler") implementation("androidx-paging-runtime") - androidTestApi("androidx-room-testing") + androidTestImplementation("androidx-room-testing") } } } diff --git a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ComposeAndroid.kt b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ComposeAndroid.kt index f2001c2..68d54ea 100644 --- a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ComposeAndroid.kt +++ b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ComposeAndroid.kt @@ -12,7 +12,7 @@ import org.gradle.api.Project * Configure Compose-specific options */ internal fun Project.configureAndroidCompose( - commonExtension: CommonExtension<*, *, *, *, *, *>, + commonExtension: CommonExtension, ) { commonExtension.apply { buildFeatures.compose = true diff --git a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ConfigureApplication.kt b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ConfigureApplication.kt index cb8f5a0..cddb225 100644 --- a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ConfigureApplication.kt +++ b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/ConfigureApplication.kt @@ -22,7 +22,6 @@ fun Project.configureApplication( ) { pluginManager.apply { apply(libs.findPluginId("application")) - apply(libs.findPluginId("kotlin")) apply(libs.findPluginId("composeCompiler")) apply(libs.findPluginId("vkompose")) apply(libs.findPluginId("serialization")) @@ -34,10 +33,10 @@ fun Project.configureApplication( val appTypePostfix = if (appType.postfix.isNotEmpty()) ".${appType.postfix}" else "" val versionNamePostfix = if (appType.postfix.isNotEmpty()) "-${appType.postfix}" else "" + extensions.configure { + arg("KOIN_CONFIG_CHECK", "true") + } extensions.configure { - extensions.configure { - arg("KOIN_CONFIG_CHECK", "true") - } configureKotlinAndroid(this) configureAndroidCompose(this) diff --git a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt index e94282e..0411961 100644 --- a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt @@ -39,7 +39,7 @@ internal fun Project.configureKotlinAndroid( * Configure base Kotlin with Android options */ private fun Project.configureKotlinAndroid( - commonExtension: CommonExtension<*, *, *, *, *, *>, + commonExtension: CommonExtension, isApp: Boolean ): Unit = with(commonExtension) { @@ -59,9 +59,10 @@ private fun Project.configureKotlinAndroid( namespace = if (moduleName.isNotEmpty()) "$APP_PREFIX.$moduleName" else APP_PREFIX - defaultConfig { + buildFeatures.buildConfig = true + + defaultConfig.apply { minSdk = libs.findVersionInt("minSdk") - buildFeatures.buildConfig = true testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" gradleLocalProperties( @@ -72,7 +73,7 @@ private fun Project.configureKotlinAndroid( } } - compileOptions { + compileOptions.apply { // Up to Java 11 APIs are available through desugaring // https://developer.android.com/studio/write/java11-minimal-support-table sourceCompatibility = JavaVersion.VERSION_21 @@ -107,8 +108,7 @@ private fun Project.configureKotlinAndroid( } } - - testOptions { unitTests.isIncludeAndroidResources = true } + testOptions.apply { unitTests.isIncludeAndroidResources = true } } /** diff --git a/build.gradle.kts b/build.gradle.kts index 62d2311..37d21bc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,14 @@ plugins { buildscript { + configurations.all { + resolutionStrategy { + // AGP 9.1.0 requires annotations:23.0.0, but Gradle 9.3.1's embedded Kotlin + // pins annotations:13.0 strictly. Force the higher version to resolve the conflict. + force("org.jetbrains:annotations:23.0.0") + } + } + repositories { google() mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f81418b..495305a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] androidDesugarJdkLibs = "2.1.5" -kotlin = "2.2.20" -ksp = "2.2.20-2.0.4" -androidGradlePlugin = "8.13.2" -androidTools = "31.13.2" +kotlin = "2.3.20" +ksp = "2.3.6" +androidGradlePlugin = "9.1.0" +androidTools = "32.1.0" minSdk = "28" targetSdk = "36" @@ -19,7 +19,7 @@ lifecycle = "2.10.0" coroutines = "1.10.2" composeBom = "2025.12.01" -composeGradle = "1.9.3" +composeGradle = "1.10.3" composeNavigation = "2.9.6" composeJunit = "1.10.0" accompanist = "0.36.0" @@ -27,7 +27,7 @@ coil = "3.3.0" composeActivity = "1.12.2" fbBom = "34.7.0" -hilt = "2.57.2" +hilt = "2.59.2" hiltCompose = "1.3.0" mockk = "1.14.7" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f82582..b0f2eb5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Aug 30 22:03:06 MSK 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 7036faf..25f0471 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} @Suppress("UnstableApiUsage") dependencyResolutionManagement { From 94c5fc336a713563258a5717468476e035f00b15 Mon Sep 17 00:00:00 2001 From: stslex Date: Mon, 23 Mar 2026 20:20:18 +0300 Subject: [PATCH 5/5] refactor: update test array types for better type safety fix: disable failing test discovery for JUnit tests Summary: This commit refactors the test code to use more specific array types, enhancing type safety. Additionally, it disables the failing test discovery feature in the test configuration to prevent build failures when no tests are discovered. --- .../kotlin/io/github/stslex/workeeper/KotlinAndroid.kt | 1 + .../core/database/migrations/Migration1To2Test.kt | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt index 0411961..ecd77ff 100644 --- a/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/io/github/stslex/workeeper/KotlinAndroid.kt @@ -103,6 +103,7 @@ private fun Project.configureKotlinAndroid( tasks.withType().configureEach { useJUnitPlatform() + failOnNoDiscoveredTests.set(false) testLogging { events("passed", "skipped", "failed") } diff --git a/core/database/src/test/kotlin/io/github/stslex/workeeper/core/database/migrations/Migration1To2Test.kt b/core/database/src/test/kotlin/io/github/stslex/workeeper/core/database/migrations/Migration1To2Test.kt index 2a96df3..7471c1c 100644 --- a/core/database/src/test/kotlin/io/github/stslex/workeeper/core/database/migrations/Migration1To2Test.kt +++ b/core/database/src/test/kotlin/io/github/stslex/workeeper/core/database/migrations/Migration1To2Test.kt @@ -140,7 +140,7 @@ class Migration1To2Test { testDb.execSQL( "INSERT INTO exercises_table (uuid, name, reps, weight, sets, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - arrayOf(testUuid, testName, testReps, testWeight, testSets, testTimestamp), + arrayOf(testUuid, testName, testReps, testWeight, testSets, testTimestamp), ) // Run migration @@ -199,7 +199,7 @@ class Migration1To2Test { exercises.forEach { exercise -> testDb.execSQL( "INSERT INTO exercises_table (uuid, name, reps, weight, sets, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - arrayOf( + arrayOf( exercise.uuid, exercise.name, exercise.reps, @@ -294,13 +294,13 @@ class Migration1To2Test { // Insert exercise with zero sets (edge case) testDb.execSQL( "INSERT INTO exercises_table (uuid, name, reps, weight, sets, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - arrayOf("uuid-zero-sets", "Test Exercise", 10, 50.0, 0, 1640995200000L), + arrayOf("uuid-zero-sets", "Test Exercise", 10, 50.0, 0, 1640995200000L), ) // Insert exercise with negative values (edge case) testDb.execSQL( "INSERT INTO exercises_table (uuid, name, reps, weight, sets, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - arrayOf("uuid-negative", "Negative Exercise", -5, -10.0, 1, 1640995200000L), + arrayOf("uuid-negative", "Negative Exercise", -5, -10.0, 1, 1640995200000L), ) // Run migration @@ -350,7 +350,7 @@ class Migration1To2Test { testDb.execSQL( "INSERT INTO exercises_table (uuid, name, reps, weight, sets, timestamp) VALUES (?, ?, ?, ?, ?, ?)", - arrayOf("test-uuid", "Test Exercise", 15, 80.5, 2, 1640995200000L), + arrayOf("test-uuid", "Test Exercise", 15, 80.5, 2, 1640995200000L), ) // Run migration