diff --git a/.editorconfig b/.editorconfig index 7be3f8784..6c8c930bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,3 +5,13 @@ ij_kotlin_allow_trailing_comma=true ij_kotlin_allow_trailing_comma_on_call_site=true ktlint_function_naming_ignore_when_annotated_with=Composable, Test +ktlint_standard_backing-property-naming = disabled +ktlint_standard_binary-expression-wrapping = disabled +ktlint_standard_chain-method-continuation = disabled +ktlint_standard_class-signature = disabled +ktlint_standard_condition-wrapping = disabled +ktlint_standard_function-expression-body = disabled +ktlint_standard_function-literal = disabled +ktlint_standard_function-type-modifier-spacing = disabled +ktlint_standard_multiline-loop = disabled +ktlint_standard_function-signature = disabled diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index dbafa68cd..a9abe496a 100644 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -17,6 +17,8 @@ org.gradle.daemon=false org.gradle.parallel=true org.gradle.workers.max=2 +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true kotlin.incremental=false diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 433a7e4a3..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,25 +0,0 @@ -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - - package-ecosystem: "gradle" - directory: "/" - schedule: - interval: "weekly" - registries: "*" - labels: [ "version update" ] - groups: - kotlin-ksp: - patterns: - - "org.jetbrains.kotlin:*" - - "org.jetbrains.kotlin.jvm" - - "com.google.devtools.ksp" - open-pull-requests-limit: 10 -registries: - maven-google: - type: "maven-repository" - url: "https://maven.google.com" - replaces-base: true diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 000000000..0eec647d9 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>android/.github:renovate-config" + ], + "baseBranches": [ + "main" + ], + "gitIgnoredAuthors": [ + "renovate[bot]@users.noreply.github.com", + "github-actions[bot]@users.noreply.github.com", + "41898282+github-actions[bot]@users.noreply.github.com" + ] +} diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index c67d3b3d8..5a212bffb 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -18,6 +18,7 @@ jobs: permissions: contents: write pull-requests: write + security-events: write timeout-minutes: 60 @@ -25,13 +26,6 @@ jobs: - name: Checkout 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 - ls /dev/kvm - - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties @@ -43,15 +37,11 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Accept licenses - run: yes | sdkmanager --licenses || true + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Check build-logic - run: ./gradlew check -p build-logic + run: ./gradlew :build-logic:convention:check - name: Check spotless run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache @@ -93,7 +83,9 @@ jobs: continue-on-error: false if: steps.screenshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository run: | - echo "::error::Screenshot tests failed, please create a PR in your fork first." && exit 1 + echo "::error::Screenshot tests failed, please create a PR in your fork first." + echo "Your fork's CI will take screenshots for your fork." + exit 1 # Runs if previous job failed - name: Generate new screenshots if verification failed and it's a PR @@ -114,19 +106,8 @@ jobs: - name: Run local tests run: ./gradlew testDemoDebug :lint:test - - name: Setup GMD - run: ./gradlew :benchmarks:pixel6Api33Setup - --info - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - - name: Build all build type and flavor permutations - run: ./gradlew :app:assemble :benchmarks:assemble -Pandroidx.baselineprofile.skipgeneration - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - -Pandroid.experimental.androidTest.numManagedDeviceShards=1 - -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 - -Pandroid.experimental.testOptions.managedDevices.setupTimeoutMinutes=5 + run: ./gradlew :app:assemble - name: Upload build outputs (APKs) uses: actions/upload-artifact@v4 @@ -135,14 +116,14 @@ jobs: path: '**/build/outputs/apk/**/*.apk' - name: Upload JVM local results (XML) - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: local-test-results path: '**/build/test-results/test*UnitTest/**.xml' - name: Upload screenshot results (PNG) - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: screenshot-test-results @@ -152,12 +133,18 @@ jobs: run: ./gradlew :app:lintProdRelease :app-nia-catalog:lintRelease :lint:lint - name: Upload lint reports (HTML) - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: lint-reports path: '**/build/reports/lint-results-*.html' + - name: Upload lint reports (SARIF) + if: ${{ !cancelled() && hashFiles('**/*.sarif') != '' }} + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: './' + - name: Check badging run: ./gradlew :app:checkProdReleaseBadging @@ -166,7 +153,7 @@ jobs: timeout-minutes: 55 strategy: matrix: - api-level: [26, 30] + api-level: [26, 34] steps: - name: Delete unnecessary tools 🔧 @@ -202,8 +189,7 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: - validate-wrappers: true - gradle-home-cache-cleanup: true + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Build projects and run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 @@ -226,7 +212,7 @@ jobs: run: ./gradlew createDemoDebugCombinedCoverageReport - name: Upload test reports - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: test-reports-${{ matrix.api-level }} @@ -235,7 +221,7 @@ jobs: - name: Display local test coverage (only API 30) if: matrix.api-level == 30 id: jacoco - uses: madrapps/jacoco-report@v1.7.0 + uses: madrapps/jacoco-report@v1.7.1 with: title: Combined test coverage report min-coverage-overall: 40 diff --git a/.github/workflows/NightlyBaselineProfiles.yaml b/.github/workflows/NightlyBaselineProfiles.yaml index 288842ac7..c8e2651c5 100644 --- a/.github/workflows/NightlyBaselineProfiles.yaml +++ b/.github/workflows/NightlyBaselineProfiles.yaml @@ -7,6 +7,7 @@ on: jobs: baseline_profiles: name: "Generate Baseline Profiles" + if: github.repository == 'android/nowinandroid' runs-on: ubuntu-latest permissions: @@ -36,6 +37,8 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Setup Android SDK uses: android-actions/setup-android@v3 @@ -44,7 +47,7 @@ jobs: run: yes | sdkmanager --licenses || true - name: Check build-logic - run: ./gradlew check -p build-logic + run: ./gradlew :build-logic:convention:check - name: Setup GMD run: ./gradlew :benchmarks:pixel6Api33Setup @@ -52,8 +55,9 @@ jobs: -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - - name: Build all build type and flavor permutations including baseline profiles - run: ./gradlew :app:assemble - -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=baselineprofile - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true + # This generates both baseline and startup profile and adds them into the generated folder + - name: Generate Baseline Profile + run: ./gradlew :app:generateReleaseBaselineProfile + -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=baselineprofile + -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" + --stacktrace diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 71ee060e1..b952ccb50 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -7,6 +7,7 @@ on: jobs: build: + if: github.repository == 'android/nowinandroid' runs-on: ubuntu-latest timeout-minutes: 120 @@ -33,6 +34,8 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Setup Android SDK uses: android-actions/setup-android@v3 diff --git a/README.md b/README.md index 1f5270323..1e2dfde17 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,9 @@ To run the tests execute the following gradle tasks: (see below for explanation). To avoid this, run `recordRoborazziDemoDebug` prior to running unit tests. - `connectedDemoDebugAndroidTest` run all instrumented tests against the `demoDebug` variant. -**Note:** You should not run `./gradlew test` or `./gradlew connectedAndroidTest` as this will execute -tests against _all_ build variants which is both unecessary and will result in failures as only the +> [!NOTE] +> You should not run `./gradlew test` or `./gradlew connectedAndroidTest` as this will execute +tests against _all_ build variants which is both unnecessary and will result in failures as only the `demoDebug` variant is supported. No other variants have any tests (although this might change in future). ## Screenshot tests @@ -138,7 +139,9 @@ stored in `modulename/src/test/screenshots`. - `compareRoborazziDemoDebug` create comparison images between failed tests and the known correct images. These can also be found in `modulename/src/test/screenshots`. -**Note on failing screenshot tests:** The known correct screenshots stored in this repository are recorded on CI using Linux. Other +> [!NOTE] +> **Note on failing screenshot tests** +> The known correct screenshots stored in this repository are recorded on CI using Linux. Other platforms may (and probably will) generate slightly different images, making the screenshot tests fail. When working on a non-Linux platform, a workaround to this is to run `recordRoborazziDemoDebug` on the `main` branch before starting work. After making changes, `verifyRoborazziDemoDebug` will identify only diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 372aff8b5..f36981625 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -1,56 +1,56 @@ androidx.activity:activity-compose:1.9.3 androidx.activity:activity-ktx:1.9.3 androidx.activity:activity:1.9.3 -androidx.annotation:annotation-experimental:1.4.0 -androidx.annotation:annotation-jvm:1.8.0 -androidx.annotation:annotation:1.8.0 +androidx.annotation:annotation-experimental:1.4.1 +androidx.annotation:annotation-jvm:1.8.1 +androidx.annotation:annotation:1.8.1 androidx.appcompat:appcompat-resources:1.6.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 androidx.browser:browser:1.8.0 -androidx.collection:collection-jvm:1.4.0 -androidx.collection:collection-ktx:1.4.0 -androidx.collection:collection:1.4.0 -androidx.compose.animation:animation-android:1.7.0 -androidx.compose.animation:animation-core-android:1.7.0 -androidx.compose.animation:animation-core:1.7.0 -androidx.compose.animation:animation:1.7.0 -androidx.compose.foundation:foundation-android:1.7.0 -androidx.compose.foundation:foundation-layout-android:1.7.0 -androidx.compose.foundation:foundation-layout:1.7.0 -androidx.compose.foundation:foundation:1.7.0 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection-ktx:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.animation:animation-android:1.7.6 +androidx.compose.animation:animation-core-android:1.7.6 +androidx.compose.animation:animation-core:1.7.6 +androidx.compose.animation:animation:1.7.6 +androidx.compose.foundation:foundation-android:1.7.6 +androidx.compose.foundation:foundation-layout-android:1.7.6 +androidx.compose.foundation:foundation-layout:1.7.6 +androidx.compose.foundation:foundation:1.7.6 androidx.compose.material3.adaptive:adaptive-android:1.0.0 androidx.compose.material3.adaptive:adaptive:1.0.0 -androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0 -androidx.compose.material3:material3-adaptive-navigation-suite:1.3.0 -androidx.compose.material3:material3-android:1.3.0 -androidx.compose.material3:material3:1.3.0 -androidx.compose.material:material-icons-core-android:1.7.0 -androidx.compose.material:material-icons-core:1.7.0 -androidx.compose.material:material-icons-extended-android:1.7.0 -androidx.compose.material:material-icons-extended:1.7.0 -androidx.compose.material:material-ripple-android:1.7.0 -androidx.compose.material:material-ripple:1.7.0 -androidx.compose.runtime:runtime-android:1.7.0 -androidx.compose.runtime:runtime-saveable-android:1.7.0 -androidx.compose.runtime:runtime-saveable:1.7.0 -androidx.compose.runtime:runtime:1.7.0 -androidx.compose.ui:ui-android:1.7.0 -androidx.compose.ui:ui-geometry-android:1.7.0 -androidx.compose.ui:ui-geometry:1.7.0 -androidx.compose.ui:ui-graphics-android:1.7.0 -androidx.compose.ui:ui-graphics:1.7.0 -androidx.compose.ui:ui-text-android:1.7.0 -androidx.compose.ui:ui-text:1.7.0 -androidx.compose.ui:ui-tooling-preview-android:1.7.0 -androidx.compose.ui:ui-tooling-preview:1.7.0 -androidx.compose.ui:ui-unit-android:1.7.0 -androidx.compose.ui:ui-unit:1.7.0 -androidx.compose.ui:ui-util-android:1.7.0 -androidx.compose.ui:ui-util:1.7.0 -androidx.compose.ui:ui:1.7.0 -androidx.compose:compose-bom:2024.09.00 +androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.1 +androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1 +androidx.compose.material3:material3-android:1.3.1 +androidx.compose.material3:material3:1.3.1 +androidx.compose.material:material-icons-core-android:1.7.6 +androidx.compose.material:material-icons-core:1.7.6 +androidx.compose.material:material-icons-extended-android:1.7.6 +androidx.compose.material:material-icons-extended:1.7.6 +androidx.compose.material:material-ripple-android:1.7.6 +androidx.compose.material:material-ripple:1.7.6 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 +androidx.compose.ui:ui-android:1.7.6 +androidx.compose.ui:ui-geometry-android:1.7.6 +androidx.compose.ui:ui-geometry:1.7.6 +androidx.compose.ui:ui-graphics-android:1.7.6 +androidx.compose.ui:ui-graphics:1.7.6 +androidx.compose.ui:ui-text-android:1.7.6 +androidx.compose.ui:ui-text:1.7.6 +androidx.compose.ui:ui-tooling-preview-android:1.7.6 +androidx.compose.ui:ui-tooling-preview:1.7.6 +androidx.compose.ui:ui-unit-android:1.7.6 +androidx.compose.ui:ui-unit:1.7.6 +androidx.compose.ui:ui-util-android:1.7.6 +androidx.compose.ui:ui-util:1.7.6 +androidx.compose.ui:ui:1.7.6 +androidx.compose:compose-bom:2024.12.01 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.13.1 androidx.core:core:1.13.1 @@ -96,10 +96,10 @@ androidx.window:window-core:1.3.0 androidx.window:window:1.3.0 com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.code.findbugs:jsr305:3.0.2 -com.google.dagger:dagger-lint-aar:2.52 -com.google.dagger:dagger:2.52 -com.google.dagger:hilt-android:2.52 -com.google.dagger:hilt-core:2.52 +com.google.dagger:dagger-lint-aar:2.54 +com.google.dagger:dagger:2.54 +com.google.dagger:hilt-android:2.54 +com.google.dagger:hilt-core:2.54 com.google.guava:listenablefuture:1.0 com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.9.0 @@ -110,10 +110,10 @@ io.coil-kt:coil-compose:2.7.0 io.coil-kt:coil:2.7.0 jakarta.inject:jakarta.inject-api:2.0.1 javax.inject:javax.inject:1 -org.jetbrains.kotlin:kotlin-stdlib-common:2.0.20 +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.0 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -org.jetbrains.kotlin:kotlin-stdlib:2.0.20 +org.jetbrains.kotlin:kotlin-stdlib:2.1.0 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 @@ -121,3 +121,4 @@ org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1 org.jetbrains.kotlinx:kotlinx-datetime:0.6.1 org.jetbrains:annotations:23.0.0 +org.jspecify:jspecify:1.0.0 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e4361008..05212a28f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,9 +36,6 @@ android { // Custom test runner to set up Hilt dependency graph testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" - vectorDrawables { - useSupportLibrary = true - } } buildTypes { @@ -119,18 +116,19 @@ dependencies { testImplementation(projects.sync.syncTest) testImplementation(libs.kotlin.test) + testDemoImplementation(libs.androidx.navigation.testing) testDemoImplementation(libs.robolectric) testDemoImplementation(libs.roborazzi) testDemoImplementation(projects.core.screenshotTesting) + testDemoImplementation(projects.core.testing) - androidTestImplementation(kotlin("test")) androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.dataTest) androidTestImplementation(projects.core.datastoreTest) androidTestImplementation(libs.androidx.test.espresso.core) - androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.hilt.android.testing) + androidTestImplementation(libs.kotlin.test) baselineProfile(projects.benchmarks) } diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index c7f8c0ff6..e334aa676 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -10,59 +10,60 @@ androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 androidx.browser:browser:1.8.0 -androidx.collection:collection-jvm:1.4.2 -androidx.collection:collection-ktx:1.4.2 -androidx.collection:collection:1.4.2 -androidx.compose.animation:animation-android:1.7.0 -androidx.compose.animation:animation-core-android:1.7.0 -androidx.compose.animation:animation-core:1.7.0 -androidx.compose.animation:animation:1.7.0 -androidx.compose.foundation:foundation-android:1.7.0 -androidx.compose.foundation:foundation-layout-android:1.7.0 -androidx.compose.foundation:foundation-layout:1.7.0 -androidx.compose.foundation:foundation:1.7.0 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection-ktx:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.animation:animation-android:1.7.6 +androidx.compose.animation:animation-core-android:1.7.6 +androidx.compose.animation:animation-core:1.7.6 +androidx.compose.animation:animation:1.7.6 +androidx.compose.foundation:foundation-android:1.7.6 +androidx.compose.foundation:foundation-layout-android:1.7.6 +androidx.compose.foundation:foundation-layout:1.7.6 +androidx.compose.foundation:foundation:1.7.6 androidx.compose.material3.adaptive:adaptive-android:1.0.0 androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0 androidx.compose.material3.adaptive:adaptive-layout:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation:1.0.0 androidx.compose.material3.adaptive:adaptive:1.0.0 -androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0 -androidx.compose.material3:material3-adaptive-navigation-suite:1.3.0 -androidx.compose.material3:material3-android:1.3.0 -androidx.compose.material3:material3-window-size-class-android:1.3.0 -androidx.compose.material3:material3-window-size-class:1.3.0 -androidx.compose.material3:material3:1.3.0 -androidx.compose.material:material-icons-core-android:1.7.0 -androidx.compose.material:material-icons-core:1.7.0 -androidx.compose.material:material-icons-extended-android:1.7.0 -androidx.compose.material:material-icons-extended:1.7.0 -androidx.compose.material:material-ripple-android:1.7.0 -androidx.compose.material:material-ripple:1.7.0 -androidx.compose.runtime:runtime-android:1.7.1 -androidx.compose.runtime:runtime-saveable-android:1.7.1 -androidx.compose.runtime:runtime-saveable:1.7.1 -androidx.compose.runtime:runtime-tracing:1.0.0-beta01 -androidx.compose.runtime:runtime:1.7.1 -androidx.compose.ui:ui-android:1.7.0 -androidx.compose.ui:ui-geometry-android:1.7.0 -androidx.compose.ui:ui-geometry:1.7.0 -androidx.compose.ui:ui-graphics-android:1.7.0 -androidx.compose.ui:ui-graphics:1.7.0 -androidx.compose.ui:ui-text-android:1.7.0 -androidx.compose.ui:ui-text:1.7.0 -androidx.compose.ui:ui-tooling-preview-android:1.7.0 -androidx.compose.ui:ui-tooling-preview:1.7.0 -androidx.compose.ui:ui-unit-android:1.7.0 -androidx.compose.ui:ui-unit:1.7.0 -androidx.compose.ui:ui-util-android:1.7.0 -androidx.compose.ui:ui-util:1.7.0 -androidx.compose.ui:ui:1.7.0 -androidx.compose:compose-bom:2024.09.00 +androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.1 +androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1 +androidx.compose.material3:material3-android:1.3.1 +androidx.compose.material3:material3-window-size-class-android:1.3.1 +androidx.compose.material3:material3-window-size-class:1.3.1 +androidx.compose.material3:material3:1.3.1 +androidx.compose.material:material-icons-core-android:1.7.6 +androidx.compose.material:material-icons-core:1.7.6 +androidx.compose.material:material-icons-extended-android:1.7.6 +androidx.compose.material:material-icons-extended:1.7.6 +androidx.compose.material:material-ripple-android:1.7.6 +androidx.compose.material:material-ripple:1.7.6 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime-tracing:1.7.6 +androidx.compose.runtime:runtime:1.7.6 +androidx.compose.ui:ui-android:1.7.6 +androidx.compose.ui:ui-geometry-android:1.7.6 +androidx.compose.ui:ui-geometry:1.7.6 +androidx.compose.ui:ui-graphics-android:1.7.6 +androidx.compose.ui:ui-graphics:1.7.6 +androidx.compose.ui:ui-text-android:1.7.6 +androidx.compose.ui:ui-text:1.7.6 +androidx.compose.ui:ui-tooling-preview-android:1.7.6 +androidx.compose.ui:ui-tooling-preview:1.7.6 +androidx.compose.ui:ui-unit-android:1.7.6 +androidx.compose.ui:ui-unit:1.7.6 +androidx.compose.ui:ui-util-android:1.7.6 +androidx.compose.ui:ui-util:1.7.6 +androidx.compose.ui:ui:1.7.6 +androidx.compose:compose-bom:2024.12.01 +androidx.concurrent:concurrent-futures-ktx:1.1.0 androidx.concurrent:concurrent-futures:1.1.0 -androidx.core:core-ktx:1.13.1 +androidx.core:core-ktx:1.15.0 androidx.core:core-splashscreen:1.0.1 -androidx.core:core:1.13.1 +androidx.core:core:1.15.0 androidx.cursoradapter:cursoradapter:1.0.0 androidx.customview:customview-poolingcontainer:1.0.0 androidx.customview:customview:1.0.0 @@ -89,38 +90,38 @@ androidx.hilt:hilt-navigation:1.2.0 androidx.hilt:hilt-work:1.2.0 androidx.interpolator:interpolator:1.0.0 androidx.legacy:legacy-support-core-utils:1.0.0 -androidx.lifecycle:lifecycle-common-java8:2.8.6 -androidx.lifecycle:lifecycle-common-jvm:2.8.6 -androidx.lifecycle:lifecycle-common:2.8.6 -androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.6 -androidx.lifecycle:lifecycle-livedata-core:2.8.6 -androidx.lifecycle:lifecycle-livedata:2.8.6 -androidx.lifecycle:lifecycle-process:2.8.6 -androidx.lifecycle:lifecycle-runtime-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-compose-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-compose:2.8.6 -androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-ktx:2.8.6 -androidx.lifecycle:lifecycle-runtime:2.8.6 -androidx.lifecycle:lifecycle-service:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-android:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6 -androidx.lifecycle:lifecycle-viewmodel:2.8.6 +androidx.lifecycle:lifecycle-common-java8:2.8.7 +androidx.lifecycle:lifecycle-common-jvm:2.8.7 +androidx.lifecycle:lifecycle-common:2.8.7 +androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7 +androidx.lifecycle:lifecycle-livedata-core:2.8.7 +androidx.lifecycle:lifecycle-livedata:2.8.7 +androidx.lifecycle:lifecycle-process:2.8.7 +androidx.lifecycle:lifecycle-runtime-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-compose:2.8.7 +androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-ktx:2.8.7 +androidx.lifecycle:lifecycle-runtime:2.8.7 +androidx.lifecycle:lifecycle-service:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-android:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7 +androidx.lifecycle:lifecycle-viewmodel:2.8.7 androidx.loader:loader:1.0.0 androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 androidx.metrics:metrics-performance:1.0.0-beta01 -androidx.navigation:navigation-common-ktx:2.8.0 -androidx.navigation:navigation-common:2.8.0 -androidx.navigation:navigation-compose:2.8.0 -androidx.navigation:navigation-runtime-ktx:2.8.0 -androidx.navigation:navigation-runtime:2.8.0 +androidx.navigation:navigation-common-ktx:2.8.5 +androidx.navigation:navigation-common:2.8.5 +androidx.navigation:navigation-compose:2.8.5 +androidx.navigation:navigation-runtime-ktx:2.8.5 +androidx.navigation:navigation-runtime:2.8.5 androidx.print:print:1.0.0 androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 -androidx.profileinstaller:profileinstaller:1.3.1 +androidx.profileinstaller:profileinstaller:1.4.1 androidx.resourceinspection:resourceinspection-annotation:1.0.1 androidx.room:room-common:2.6.1 androidx.room:room-ktx:2.6.1 @@ -141,11 +142,11 @@ androidx.window.extensions.core:core:1.0.0 androidx.window:window-core-android:1.3.0 androidx.window:window-core:1.3.0 androidx.window:window:1.3.0 -androidx.work:work-runtime-ktx:2.9.0 -androidx.work:work-runtime:2.9.0 +androidx.work:work-runtime-ktx:2.10.0 +androidx.work:work-runtime:2.10.0 com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.32.0 -com.google.accompanist:accompanist-permissions:0.34.0 +com.google.accompanist:accompanist-permissions:0.37.0 com.google.android.datatransport:transport-api:3.2.0 com.google.android.datatransport:transport-backend-cct:3.3.0 com.google.android.datatransport:transport-runtime:3.3.0 @@ -153,31 +154,31 @@ com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 com.google.android.gms:play-services-cloud-messaging:17.2.0 -com.google.android.gms:play-services-measurement-api:22.1.0 -com.google.android.gms:play-services-measurement-base:22.1.0 -com.google.android.gms:play-services-measurement-impl:22.1.0 -com.google.android.gms:play-services-measurement-sdk-api:22.1.0 -com.google.android.gms:play-services-measurement-sdk:22.1.0 -com.google.android.gms:play-services-measurement:22.1.0 +com.google.android.gms:play-services-measurement-api:22.1.2 +com.google.android.gms:play-services-measurement-base:22.1.2 +com.google.android.gms:play-services-measurement-impl:22.1.2 +com.google.android.gms:play-services-measurement-sdk-api:22.1.2 +com.google.android.gms:play-services-measurement-sdk:22.1.2 +com.google.android.gms:play-services-measurement:22.1.2 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 com.google.code.findbugs:jsr305:3.0.2 -com.google.dagger:dagger-lint-aar:2.52 -com.google.dagger:dagger:2.52 -com.google.dagger:hilt-android:2.52 -com.google.dagger:hilt-core:2.52 +com.google.dagger:dagger-lint-aar:2.54 +com.google.dagger:dagger:2.54 +com.google.dagger:hilt-android:2.54 +com.google.dagger:hilt-core:2.54 com.google.errorprone:error_prone_annotations:2.26.0 com.google.firebase:firebase-abt:21.1.1 -com.google.firebase:firebase-analytics:22.1.0 +com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 -com.google.firebase:firebase-bom:33.3.0 +com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 com.google.firebase:firebase-config-interop:16.0.1 -com.google.firebase:firebase-config:22.0.0 -com.google.firebase:firebase-crashlytics:19.1.0 +com.google.firebase:firebase-config:22.0.1 +com.google.firebase:firebase-crashlytics:19.3.0 com.google.firebase:firebase-datatransport:19.0.0 com.google.firebase:firebase-encoders-json:18.0.1 com.google.firebase:firebase-encoders-proto:16.0.0 @@ -186,16 +187,15 @@ com.google.firebase:firebase-iid-interop:17.1.0 com.google.firebase:firebase-installations-interop:17.2.0 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:20.0.1 -com.google.firebase:firebase-messaging:24.0.1 -com.google.firebase:firebase-perf:21.0.1 -com.google.firebase:firebase-sessions:2.0.4 -com.google.firebase:protolite-well-known-types:18.0.0 +com.google.firebase:firebase-messaging:24.1.0 +com.google.firebase:firebase-perf:21.0.3 +com.google.firebase:firebase-sessions:2.0.7 com.google.guava:failureaccess:1.0.1 com.google.guava:guava:31.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava com.google.j2objc:j2objc-annotations:1.3 -com.google.protobuf:protobuf-javalite:4.26.1 -com.google.protobuf:protobuf-kotlin-lite:4.26.1 +com.google.protobuf:protobuf-javalite:4.29.2 +com.google.protobuf:protobuf-kotlin-lite:4.29.2 com.squareup.okhttp3:logging-interceptor:4.12.0 com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.9.0 @@ -212,21 +212,22 @@ javax.inject:javax.inject:1 org.checkerframework:checker-qual:3.12.0 org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22 org.jetbrains.kotlin:kotlin-parcelize-runtime:1.9.22 -org.jetbrains.kotlin:kotlin-stdlib-common:2.0.20 +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.0 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -org.jetbrains.kotlin:kotlin-stdlib:2.0.20 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0 -org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.9.0 -org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.9.0 +org.jetbrains.kotlin:kotlin-stdlib:2.1.0 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1 +org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.10.1 +org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.1 org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1 org.jetbrains.kotlinx:kotlinx-datetime:0.6.1 -org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3 -org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3 -org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3 -org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.3 -org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3 +org.jetbrains.kotlinx:kotlinx-serialization-bom:1.8.0 +org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.8.0 +org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0 +org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.8.0 +org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0 org.jetbrains:annotations:23.0.0 +org.jspecify:jspecify:1.0.0 diff --git a/app/prodRelease-badging.txt b/app/prodRelease-badging.txt index 9908e775a..0d770604e 100644 --- a/app/prodRelease-badging.txt +++ b/app/prodRelease-badging.txt @@ -1,6 +1,6 @@ -package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14' +package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15' sdkVersion:'21' -targetSdkVersion:'34' +targetSdkVersion:'35' uses-permission: name='android.permission.INTERNET' uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.POST_NOTIFICATIONS' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5b22f9865..af3534342 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 599bd0b35..ecc23d80e 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -22,10 +22,7 @@ import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -35,21 +32,22 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.metrics.performance.JankStats +import androidx.tracing.trace import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading -import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme -import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig -import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone import com.google.samples.apps.nowinandroid.ui.NiaApp import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState +import com.google.samples.apps.nowinandroid.util.isSystemInDarkTheme import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject @@ -81,53 +79,60 @@ class MainActivity : ComponentActivity() { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) - var uiState: MainActivityUiState by mutableStateOf(Loading) + // We keep this as a mutable state, so that we can track changes inside the composition. + // This allows us to react to dark/light mode changes. + var themeSettings by mutableStateOf( + ThemeSettings( + darkTheme = resources.configuration.isSystemInDarkTheme, + androidTheme = Loading.shouldUseAndroidTheme, + disableDynamicTheming = Loading.shouldDisableDynamicTheming, + ), + ) // Update the uiState lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.uiState - .onEach { uiState = it } - .collect() + combine( + isSystemInDarkTheme(), + viewModel.uiState, + ) { systemDark, uiState -> + ThemeSettings( + darkTheme = uiState.shouldUseDarkTheme(systemDark), + androidTheme = uiState.shouldUseAndroidTheme, + disableDynamicTheming = uiState.shouldDisableDynamicTheming, + ) + } + .onEach { themeSettings = it } + .map { it.darkTheme } + .distinctUntilChanged() + .collect { darkTheme -> + trace("niaEdgeToEdge") { + // Turn off the decor fitting system windows, which allows us to handle insets, + // including IME animations, and go edge-to-edge. + // This is the same parameters as the default enableEdgeToEdge call, but we manually + // resolve whether or not to show dark theme using uiState, since it can be different + // than the configuration's dark theme value based on the user preference. + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + lightScrim = android.graphics.Color.TRANSPARENT, + darkScrim = android.graphics.Color.TRANSPARENT, + ) { darkTheme }, + navigationBarStyle = SystemBarStyle.auto( + lightScrim = lightScrim, + darkScrim = darkScrim, + ) { darkTheme }, + ) + } + } } } // Keep the splash screen on-screen until the UI state is loaded. This condition is // evaluated each time the app needs to be redrawn so it should be fast to avoid blocking // the UI. - splashScreen.setKeepOnScreenCondition { - when (uiState) { - Loading -> true - is Success -> false - } - } - - // Turn off the decor fitting system windows, which allows us to handle insets, - // including IME animations, and go edge-to-edge - // This also sets up the initial system bar style based on the platform theme - enableEdgeToEdge() + splashScreen.setKeepOnScreenCondition { viewModel.uiState.value.shouldKeepSplashScreen() } setContent { - val darkTheme = shouldUseDarkTheme(uiState) - - // Update the edge to edge configuration to match the theme - // This is the same parameters as the default enableEdgeToEdge call, but we manually - // resolve whether or not to show dark theme using uiState, since it can be different - // than the configuration's dark theme value based on the user preference. - DisposableEffect(darkTheme) { - enableEdgeToEdge( - statusBarStyle = SystemBarStyle.auto( - android.graphics.Color.TRANSPARENT, - android.graphics.Color.TRANSPARENT, - ) { darkTheme }, - navigationBarStyle = SystemBarStyle.auto( - lightScrim, - darkScrim, - ) { darkTheme }, - ) - onDispose {} - } - val appState = rememberNiaAppState( networkMonitor = networkMonitor, userNewsResourceRepository = userNewsResourceRepository, @@ -141,9 +146,9 @@ class MainActivity : ComponentActivity() { LocalTimeZone provides currentTimeZone, ) { NiaTheme( - darkTheme = darkTheme, - androidTheme = shouldUseAndroidTheme(uiState), - disableDynamicTheming = shouldDisableDynamicTheming(uiState), + darkTheme = themeSettings.darkTheme, + androidTheme = themeSettings.androidTheme, + disableDynamicTheming = themeSettings.disableDynamicTheming, ) { NiaApp(appState) } @@ -162,47 +167,6 @@ class MainActivity : ComponentActivity() { } } -/** - * Returns `true` if the Android theme should be used, as a function of the [uiState]. - */ -@Composable -private fun shouldUseAndroidTheme( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> false - is Success -> when (uiState.userData.themeBrand) { - ThemeBrand.DEFAULT -> false - ThemeBrand.ANDROID -> true - } -} - -/** - * Returns `true` if the dynamic color is disabled, as a function of the [uiState]. - */ -@Composable -private fun shouldDisableDynamicTheming( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> false - is Success -> !uiState.userData.useDynamicColor -} - -/** - * Returns `true` if dark theme should be used, as a function of the [uiState] and the - * current system context. - */ -@Composable -private fun shouldUseDarkTheme( - uiState: MainActivityUiState, -): Boolean = when (uiState) { - Loading -> isSystemInDarkTheme() - is Success -> when (uiState.userData.darkThemeConfig) { - DarkThemeConfig.FOLLOW_SYSTEM -> isSystemInDarkTheme() - DarkThemeConfig.LIGHT -> false - DarkThemeConfig.DARK -> true - } -} - /** * The default light scrim, as defined by androidx and the platform: * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598 @@ -214,3 +178,13 @@ private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF) * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598 */ private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) + +/** + * Class for the system theme settings. + * This wrapping class allows us to combine all the changes and prevent unnecessary recompositions. + */ +data class ThemeSettings( + val darkTheme: Boolean, + val androidTheme: Boolean, + val disableDynamicTheming: Boolean, +) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt index 09f4597a7..2d22b7d9c 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt @@ -21,6 +21,8 @@ import androidx.lifecycle.viewModelScope import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository +import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig +import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.UserData import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted @@ -44,5 +46,40 @@ class MainActivityViewModel @Inject constructor( sealed interface MainActivityUiState { data object Loading : MainActivityUiState - data class Success(val userData: UserData) : MainActivityUiState + + data class Success(val userData: UserData) : MainActivityUiState { + override val shouldDisableDynamicTheming = !userData.useDynamicColor + + override val shouldUseAndroidTheme: Boolean = when (userData.themeBrand) { + ThemeBrand.DEFAULT -> false + ThemeBrand.ANDROID -> true + } + + override fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) = + when (userData.darkThemeConfig) { + DarkThemeConfig.FOLLOW_SYSTEM -> isSystemDarkTheme + DarkThemeConfig.LIGHT -> false + DarkThemeConfig.DARK -> true + } + } + + /** + * Returns `true` if the state wasn't loaded yet and it should keep showing the splash screen. + */ + fun shouldKeepSplashScreen() = this is Loading + + /** + * Returns `true` if the dynamic color is disabled. + */ + val shouldDisableDynamicTheming: Boolean get() = true + + /** + * Returns `true` if the Android theme should be used. + */ + val shouldUseAndroidTheme: Boolean get() = false + + /** + * Returns `true` if dark theme should be used. + */ + fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) = isSystemDarkTheme } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt index 8e3ad814a..77f72e5fc 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt @@ -17,6 +17,9 @@ package com.google.samples.apps.nowinandroid import android.app.Application +import android.content.pm.ApplicationInfo +import android.os.StrictMode +import android.os.StrictMode.ThreadPolicy.Builder import coil.ImageLoader import coil.ImageLoaderFactory import com.google.samples.apps.nowinandroid.sync.initializers.Sync @@ -37,10 +40,34 @@ class NiaApplication : Application(), ImageLoaderFactory { override fun onCreate() { super.onCreate() + + setStrictModePolicy() + // Initialize Sync; the system responsible for keeping data in the app up to date. Sync.initialize(context = this) profileVerifierLogger() } override fun newImageLoader(): ImageLoader = imageLoader.get() + + /** + * Return true if the application is debuggable. + */ + private fun isDebuggable(): Boolean { + return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE + } + + /** + * Set a thread policy that detects all potential problems on the main thread, such as network + * and disk access. + * + * If a problem is found, the offending call will be logged and the application will be killed. + */ + private fun setStrictModePolicy() { + if (isDebuggable()) { + StrictMode.setThreadPolicy( + Builder().detectAll().penaltyLog().penaltyDeath().build(), + ) + } + } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 640b22e83..f27b90cbe 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -37,7 +37,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult.ActionPerformed import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -76,7 +75,6 @@ import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import kotlin.reflect.KClass import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR -@OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun NiaApp( appState: NiaAppState, @@ -126,7 +124,6 @@ fun NiaApp( @OptIn( ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, - ExperimentalMaterial3AdaptiveApi::class, ) internal fun NiaApp( appState: NiaAppState, diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index 249f07590..7c892c854 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -18,6 +18,8 @@ package com.google.samples.apps.nowinandroid.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController @@ -25,7 +27,6 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import androidx.tracing.trace @@ -83,9 +84,21 @@ class NiaAppState( userNewsResourceRepository: UserNewsResourceRepository, timeZoneMonitor: TimeZoneMonitor, ) { + private val previousDestination = mutableStateOf(null) + val currentDestination: NavDestination? - @Composable get() = navController - .currentBackStackEntryAsState().value?.destination + @Composable get() { + // Collect the currentBackStackEntryFlow as a state + val currentEntry = navController.currentBackStackEntryFlow + .collectAsState(initial = null) + + // Fallback to previousDestination if currentEntry is null + return currentEntry.value?.destination.also { destination -> + if (destination != null) { + previousDestination.value = destination + } + } ?: previousDestination.value + } val currentTopLevelDestination: TopLevelDestination? @Composable get() { diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/UiExtensions.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/UiExtensions.kt new file mode 100644 index 000000000..20d55ab4c --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/UiExtensions.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.util + +import android.content.res.Configuration +import androidx.activity.ComponentActivity +import androidx.core.util.Consumer +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged + +/** + * Convenience wrapper for dark mode checking + */ +val Configuration.isSystemInDarkTheme + get() = (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + +/** + * Registers listener for configuration changes to retrieve whether system is in dark theme or not. + * Immediately upon subscribing, it sends the current value and then registers listener for changes. + */ +fun ComponentActivity.isSystemInDarkTheme() = callbackFlow { + channel.trySend(resources.configuration.isSystemInDarkTheme) + + val listener = Consumer { + channel.trySend(it.isSystemInDarkTheme) + } + + addOnConfigurationChangedListener(listener) + + awaitClose { removeOnConfigurationChangedListener(listener) } +} + .distinctUntilChanged() + .conflate() diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index e84b96b73..9c9488fde 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -16,7 +16,6 @@ package com.google.samples.apps.nowinandroid.ui -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider @@ -113,7 +112,6 @@ class NiaAppScreenSizesScreenshotTests { TimeZone.setDefault(TimeZone.getTimeZone("UTC")) } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testNiaAppScreenshotWithSize(width: Dp, height: Dp, screenshotName: String) { composeTestRule.setContent { CompositionLocalProvider( diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt similarity index 94% rename from app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt rename to app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt index c2c74458d..c6ddb54fb 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt @@ -31,6 +31,8 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepo import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -38,15 +40,18 @@ import kotlinx.coroutines.test.runTest import kotlinx.datetime.TimeZone import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config import kotlin.test.assertEquals import kotlin.test.assertTrue /** * Tests [NiaAppState]. - * - * Note: This could become an unit test if Robolectric is added to the project and the Context - * is faked. */ +@RunWith(RobolectricTestRunner::class) +@Config(application = HiltTestApplication::class) +@HiltAndroidTest class NiaAppStateTest { @get:Rule diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt index 2ef0d3e4f..78f568e03 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt @@ -34,7 +34,6 @@ import androidx.compose.foundation.layout.windowInsetsStartWidth import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.material3.SnackbarDuration.Indefinite import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -209,7 +208,6 @@ class SnackbarInsetsScreenshotTests { } } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testSnackbarScreenshotWithSize( snackbarHostState: SnackbarHostState, width: Dp, diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt index fe2e98452..b9b1047c1 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt @@ -19,7 +19,6 @@ package com.google.samples.apps.nowinandroid.ui import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.SnackbarDuration.Indefinite import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider @@ -182,7 +181,6 @@ class SnackbarScreenshotTests { } } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testSnackbarScreenshotWithSize( snackbarHostState: SnackbarHostState, width: Dp, diff --git a/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png index 912fca4c7..2e6d1037c 100644 Binary files a/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png index 30873b584..1acb34aba 100644 Binary files a/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png index 668d69146..d52c844a3 100644 Binary files a/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationBar.png index 1daf5ec34..e6e574347 100644 Binary files a/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png index e2dffaf01..222fc21aa 100644 Binary files a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png index c5b7fe883..a33ad8863 100644 Binary files a/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/insets_snackbar_compact_medium.png b/app/src/testDemo/screenshots/insets_snackbar_compact_medium.png index aae785a47..4e741bdcc 100644 Binary files a/app/src/testDemo/screenshots/insets_snackbar_compact_medium.png and b/app/src/testDemo/screenshots/insets_snackbar_compact_medium.png differ diff --git a/app/src/testDemo/screenshots/insets_snackbar_compact_medium_noSnackbar.png b/app/src/testDemo/screenshots/insets_snackbar_compact_medium_noSnackbar.png index d37f02c65..2e76d3a68 100644 Binary files a/app/src/testDemo/screenshots/insets_snackbar_compact_medium_noSnackbar.png and b/app/src/testDemo/screenshots/insets_snackbar_compact_medium_noSnackbar.png differ diff --git a/app/src/testDemo/screenshots/insets_snackbar_expanded_expanded.png b/app/src/testDemo/screenshots/insets_snackbar_expanded_expanded.png index 253b6be4c..d39df7b79 100644 Binary files a/app/src/testDemo/screenshots/insets_snackbar_expanded_expanded.png and b/app/src/testDemo/screenshots/insets_snackbar_expanded_expanded.png differ diff --git a/app/src/testDemo/screenshots/insets_snackbar_medium_medium.png b/app/src/testDemo/screenshots/insets_snackbar_medium_medium.png index 3e7171bf4..ec6377143 100644 Binary files a/app/src/testDemo/screenshots/insets_snackbar_medium_medium.png and b/app/src/testDemo/screenshots/insets_snackbar_medium_medium.png differ diff --git a/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationBar.png index 4bc5d2b1c..fdbf9ee8b 100644 Binary files a/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png index 79f808f44..882e62760 100644 Binary files a/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png index f914a0454..8ffe73e86 100644 Binary files a/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/snackbar_compact_medium.png b/app/src/testDemo/screenshots/snackbar_compact_medium.png index 7676de40a..3f67285a3 100644 Binary files a/app/src/testDemo/screenshots/snackbar_compact_medium.png and b/app/src/testDemo/screenshots/snackbar_compact_medium.png differ diff --git a/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png b/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png index ff9ed7669..893d43d19 100644 Binary files a/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png and b/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png differ diff --git a/app/src/testDemo/screenshots/snackbar_expanded_expanded.png b/app/src/testDemo/screenshots/snackbar_expanded_expanded.png index 841a02eaf..b429a7315 100644 Binary files a/app/src/testDemo/screenshots/snackbar_expanded_expanded.png and b/app/src/testDemo/screenshots/snackbar_expanded_expanded.png differ diff --git a/app/src/testDemo/screenshots/snackbar_medium_medium.png b/app/src/testDemo/screenshots/snackbar_medium_medium.png index 2800575b8..00e9e3051 100644 Binary files a/app/src/testDemo/screenshots/snackbar_medium_medium.png and b/app/src/testDemo/screenshots/snackbar_medium_medium.png differ diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index c735fa4a0..6d0237010 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -15,10 +15,10 @@ */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { `kotlin-dsl` + alias(libs.plugins.android.lint) } group = "com.google.samples.apps.nowinandroid.buildlogic" @@ -46,6 +46,7 @@ dependencies { compileOnly(libs.ksp.gradlePlugin) compileOnly(libs.room.gradlePlugin) implementation(libs.truth) + lintChecks(libs.androidx.lint.gradle) } tasks { @@ -58,59 +59,59 @@ tasks { gradlePlugin { plugins { register("androidApplicationCompose") { - id = "nowinandroid.android.application.compose" + id = libs.plugins.nowinandroid.android.application.compose.get().pluginId implementationClass = "AndroidApplicationComposeConventionPlugin" } register("androidApplication") { - id = "nowinandroid.android.application" + id = libs.plugins.nowinandroid.android.application.asProvider().get().pluginId implementationClass = "AndroidApplicationConventionPlugin" } register("androidApplicationJacoco") { - id = "nowinandroid.android.application.jacoco" + id = libs.plugins.nowinandroid.android.application.jacoco.get().pluginId implementationClass = "AndroidApplicationJacocoConventionPlugin" } register("androidLibraryCompose") { - id = "nowinandroid.android.library.compose" + id = libs.plugins.nowinandroid.android.library.compose.get().pluginId implementationClass = "AndroidLibraryComposeConventionPlugin" } register("androidLibrary") { - id = "nowinandroid.android.library" + id = libs.plugins.nowinandroid.android.library.asProvider().get().pluginId implementationClass = "AndroidLibraryConventionPlugin" } register("androidFeature") { - id = "nowinandroid.android.feature" + id = libs.plugins.nowinandroid.android.feature.get().pluginId implementationClass = "AndroidFeatureConventionPlugin" } register("androidLibraryJacoco") { - id = "nowinandroid.android.library.jacoco" + id = libs.plugins.nowinandroid.android.library.jacoco.get().pluginId implementationClass = "AndroidLibraryJacocoConventionPlugin" } register("androidTest") { - id = "nowinandroid.android.test" + id = libs.plugins.nowinandroid.android.test.get().pluginId implementationClass = "AndroidTestConventionPlugin" } register("hilt") { - id = "nowinandroid.hilt" + id = libs.plugins.nowinandroid.hilt.get().pluginId implementationClass = "HiltConventionPlugin" } register("androidRoom") { - id = "nowinandroid.android.room" + id = libs.plugins.nowinandroid.android.room.get().pluginId implementationClass = "AndroidRoomConventionPlugin" } register("androidFirebase") { - id = "nowinandroid.android.application.firebase" + id = libs.plugins.nowinandroid.android.application.firebase.get().pluginId implementationClass = "AndroidApplicationFirebaseConventionPlugin" } register("androidFlavors") { - id = "nowinandroid.android.application.flavors" + id = libs.plugins.nowinandroid.android.application.flavors.get().pluginId implementationClass = "AndroidApplicationFlavorsConventionPlugin" } register("androidLint") { - id = "nowinandroid.android.lint" + id = libs.plugins.nowinandroid.android.lint.get().pluginId implementationClass = "AndroidLintConventionPlugin" } register("jvmLibrary") { - id = "nowinandroid.jvm.library" + id = libs.plugins.nowinandroid.jvm.library.get().pluginId implementationClass = "JvmLibraryConventionPlugin" } } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index f4d5bb0d0..1ab3a2ca0 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -23,22 +23,21 @@ import com.google.samples.apps.nowinandroid.configureKotlinAndroid import com.google.samples.apps.nowinandroid.configurePrintApksTask import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.getByType class AndroidApplicationConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") - apply("nowinandroid.android.lint") - apply("com.dropbox.dependency-guard") - } + apply(plugin = "com.android.application") + apply(plugin = "org.jetbrains.kotlin.android") + apply(plugin = "nowinandroid.android.lint") + apply(plugin = "com.dropbox.dependency-guard") extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 34 + defaultConfig.targetSdk = 35 @Suppress("UnstableApiUsage") testOptions.animationsDisabled = true configureGradleManagedDevices(this) @@ -49,5 +48,4 @@ class AndroidApplicationConventionPlugin : Plugin { } } } - } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt index 422592b8a..be3ec0365 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -19,23 +19,32 @@ import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.exclude class AndroidApplicationFirebaseConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("com.google.gms.google-services") - apply("com.google.firebase.firebase-perf") - apply("com.google.firebase.crashlytics") - } + apply(plugin = "com.google.gms.google-services") + apply(plugin = "com.google.firebase.firebase-perf") + apply(plugin = "com.google.firebase.crashlytics") dependencies { val bom = libs.findLibrary("firebase-bom").get() - add("implementation", platform(bom)) + "implementation"(platform(bom)) "implementation"(libs.findLibrary("firebase.analytics").get()) - "implementation"(libs.findLibrary("firebase.performance").get()) + "implementation"(libs.findLibrary("firebase.performance").get()) { + /* + Exclusion of protobuf / protolite dependencies is necessary as the + datastore-proto brings in protobuf dependencies. These are the source of truth + for Now in Android. + That's why the duplicate classes from below dependencies are excluded. + */ + exclude(group = "com.google.protobuf", module = "protobuf-javalite") + exclude(group = "com.google.firebase", module = "protolite-well-known-types") + } "implementation"(libs.findLibrary("firebase.crashlytics").get()) } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt index ac385b0d9..b0eece41d 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt @@ -14,18 +14,20 @@ * limitations under the License. */ +import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.variant.ApplicationAndroidComponentsExtension -import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.getByType class AndroidApplicationJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply("jacoco") - val androidExtension = extensions.getByType() + apply(plugin = "jacoco") + + val androidExtension = extensions.getByType() androidExtension.buildTypes.configureEach { enableAndroidTestCoverage = true diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 6d0f213d4..1af5523c5 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -19,35 +19,37 @@ import com.google.samples.apps.nowinandroid.configureGradleManagedDevices import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies class AndroidFeatureConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply { - apply("nowinandroid.android.library") - apply("nowinandroid.hilt") - apply("org.jetbrains.kotlin.plugin.serialization") - } + apply(plugin = "nowinandroid.android.library") + apply(plugin = "nowinandroid.hilt") + apply(plugin = "org.jetbrains.kotlin.plugin.serialization") + extensions.configure { testOptions.animationsDisabled = true configureGradleManagedDevices(this) } dependencies { - add("implementation", project(":core:ui")) - add("implementation", project(":core:designsystem")) + "implementation"(project(":core:ui")) + "implementation"(project(":core:designsystem")) - add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) - add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) - add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) - add("implementation", libs.findLibrary("androidx.navigation.compose").get()) - add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) - add("implementation", libs.findLibrary("kotlinx.serialization.json").get()) + "implementation"(libs.findLibrary("androidx.hilt.navigation.compose").get()) + "implementation"(libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) + "implementation"(libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) + "implementation"(libs.findLibrary("androidx.navigation.compose").get()) + "implementation"(libs.findLibrary("androidx.tracing.ktx").get()) + "implementation"(libs.findLibrary("kotlinx.serialization.json").get()) - add("testImplementation", libs.findLibrary("androidx.navigation.testing").get()) - add("androidTestImplementation", libs.findLibrary("androidx.lifecycle.runtimeTesting").get()) + "testImplementation"(libs.findLibrary("androidx.navigation.testing").get()) + "androidTestImplementation"( + libs.findLibrary("androidx.lifecycle.runtimeTesting").get(), + ) } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 71d818c0c..3fe727410 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -24,39 +24,39 @@ import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.kotlin class AndroidLibraryConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - apply("nowinandroid.android.lint") - } + apply(plugin = "com.android.library") + apply(plugin = "org.jetbrains.kotlin.android") + apply(plugin = "nowinandroid.android.lint") extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 34 + defaultConfig.targetSdk = 35 defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testOptions.animationsDisabled = true configureFlavors(this) configureGradleManagedDevices(this) // The resource prefix is derived from the module name, // so resources inside ":core:module1" must be prefixed with "core_module1_" - resourcePrefix = path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_").lowercase() + "_" + resourcePrefix = + path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_") + .lowercase() + "_" } extensions.configure { configurePrintApksTask(this) disableUnnecessaryAndroidTests(target) } dependencies { - add("androidTestImplementation", kotlin("test")) - add("testImplementation", kotlin("test")) + "androidTestImplementation"(libs.findLibrary("kotlin.test").get()) + "testImplementation"(libs.findLibrary("kotlin.test").get()) - add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) + "implementation"(libs.findLibrary("androidx.tracing.ktx").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt index 6f2ff60c5..d249e4cbf 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt @@ -15,17 +15,18 @@ */ import com.android.build.api.dsl.LibraryExtension -import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.getByType class AndroidLibraryJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply("jacoco") + apply(plugin = "jacoco") + val androidExtension = extensions.getByType() androidExtension.buildTypes.configureEach { diff --git a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt index 1734df930..884d6f076 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt @@ -19,6 +19,7 @@ import com.android.build.api.dsl.LibraryExtension import com.android.build.api.dsl.Lint import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure class AndroidLintConventionPlugin : Plugin { @@ -32,7 +33,7 @@ class AndroidLintConventionPlugin : Plugin { configure { lint(Lint::configure) } else -> { - pluginManager.apply("com.android.lint") + apply(plugin = "com.android.lint") configure(Lint::configure) } } @@ -42,5 +43,7 @@ class AndroidLintConventionPlugin : Plugin { private fun Lint.configure() { xmlReport = true + sarifReport = true checkDependencies = true + disable += "GradleDependency" } diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index dbca79a5e..6919b5e5d 100644 --- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -19,6 +19,7 @@ import com.google.devtools.ksp.gradle.KspExtension import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies @@ -26,8 +27,8 @@ class AndroidRoomConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply("androidx.room") - pluginManager.apply("com.google.devtools.ksp") + apply(plugin = "androidx.room") + apply(plugin = "com.google.devtools.ksp") extensions.configure { arg("room.generateKotlin", "true") @@ -41,10 +42,10 @@ class AndroidRoomConventionPlugin : Plugin { } dependencies { - add("implementation", libs.findLibrary("room.runtime").get()) - add("implementation", libs.findLibrary("room.ktx").get()) - add("ksp", libs.findLibrary("room.compiler").get()) + "implementation"(libs.findLibrary("room.runtime").get()) + "implementation"(libs.findLibrary("room.ktx").get()) + "ksp"(libs.findLibrary("room.compiler").get()) } } } -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt index e48d75757..67933f77d 100644 --- a/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt @@ -19,22 +19,20 @@ import com.google.samples.apps.nowinandroid.configureGradleManagedDevices import com.google.samples.apps.nowinandroid.configureKotlinAndroid import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure class AndroidTestConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("com.android.test") - apply("org.jetbrains.kotlin.android") - } + apply(plugin = "com.android.test") + apply(plugin = "org.jetbrains.kotlin.android") extensions.configure { configureKotlinAndroid(this) - defaultConfig.targetSdk = 34 + defaultConfig.targetSdk = 35 configureGradleManagedDevices(this) } } } - } diff --git a/build-logic/convention/src/main/kotlin/HiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/HiltConventionPlugin.kt index f49366f75..5a90ff98f 100644 --- a/build-logic/convention/src/main/kotlin/HiltConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/HiltConventionPlugin.kt @@ -18,28 +18,30 @@ import com.android.build.gradle.api.AndroidBasePlugin import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.dependencies class HiltConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - pluginManager.apply("com.google.devtools.ksp") + apply(plugin = "com.google.devtools.ksp") + dependencies { - add("ksp", libs.findLibrary("hilt.compiler").get()) + "ksp"(libs.findLibrary("hilt.compiler").get()) } // Add support for Jvm Module, base on org.jetbrains.kotlin.jvm pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { dependencies { - add("implementation", libs.findLibrary("hilt.core").get()) + "implementation"(libs.findLibrary("hilt.core").get()) } } /** Add support for Android modules, based on [AndroidBasePlugin] */ pluginManager.withPlugin("com.android.base") { - pluginManager.apply("dagger.hilt.android.plugin") + apply(plugin = "dagger.hilt.android.plugin") dependencies { - add("implementation", libs.findLibrary("hilt.android").get()) + "implementation"(libs.findLibrary("hilt.android").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt index 652409db6..a1477891d 100644 --- a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt @@ -15,21 +15,21 @@ */ import com.google.samples.apps.nowinandroid.configureKotlinJvm +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.kotlin class JvmLibraryConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("org.jetbrains.kotlin.jvm") - apply("nowinandroid.android.lint") - } + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "nowinandroid.android.lint") + configureKotlinJvm() dependencies { - add("testImplementation", kotlin("test")) + "testImplementation"(libs.findLibrary("kotlin.test").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt index ffb6358c3..ed2a5289b 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt @@ -37,10 +37,10 @@ internal fun Project.configureAndroidCompose( dependencies { val bom = libs.findLibrary("androidx-compose-bom").get() - add("implementation", platform(bom)) - add("androidTestImplementation", platform(bom)) - add("implementation", libs.findLibrary("androidx-compose-ui-tooling-preview").get()) - add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get()) + "implementation"(platform(bom)) + "androidTestImplementation"(platform(bom)) + "implementation"(libs.findLibrary("androidx-compose-ui-tooling-preview").get()) + "debugImplementation"(libs.findLibrary("androidx-compose-ui-tooling").get()) } testOptions { @@ -53,8 +53,10 @@ internal fun Project.configureAndroidCompose( extensions.configure { fun Provider.onlyIfTrue() = flatMap { provider { it.takeIf(String::toBoolean) } } - fun Provider<*>.relativeToRootProject(dir: String) = flatMap { - rootProject.layout.buildDirectory.dir(projectDir.toRelativeString(rootDir)) + fun Provider<*>.relativeToRootProject(dir: String) = map { + isolated.rootProject.projectDirectory + .dir("build") + .dir(projectDir.toRelativeString(rootDir)) }.map { it.dir(dir) } project.providers.gradleProperty("enableComposeCompilerMetrics").onlyIfTrue() @@ -65,7 +67,7 @@ internal fun Project.configureAndroidCompose( .relativeToRootProject("compose-reports") .let(reportsDestination::set) - stabilityConfigurationFile = - rootProject.layout.projectDirectory.file("compose_compiler_config.conf") + stabilityConfigurationFiles + .add(isolated.rootProject.projectDirectory.file("compose_compiler_config.conf")) } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt index d0c26e4e6..c51dac5c9 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt @@ -30,6 +30,6 @@ import org.gradle.api.Project internal fun LibraryAndroidComponentsExtension.disableUnnecessaryAndroidTests( project: Project, ) = beforeVariants { - it.enableAndroidTest = it.enableAndroidTest + it.androidTest.enable = it.androidTest.enable && project.projectDir.resolve("src/androidTest").exists() } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt index 9eabff31b..886c70625 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt @@ -39,8 +39,6 @@ import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.register import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.process.ExecOperations -import java.io.File -import java.util.Locale import javax.inject.Inject @CacheableTask @@ -123,15 +121,17 @@ fun Project.configureBadgingTasks( val generateBadging = tasks.register(generateBadgingTaskName) { apk = variant.artifacts.get(SingleArtifact.APK_FROM_BUNDLE) - - aapt2Executable = File( - baseExtension.sdkDirectory, - "${SdkConstants.FD_BUILD_TOOLS}/" + - "${baseExtension.buildToolsVersion}/" + - SdkConstants.FN_AAPT2, + aapt2Executable.set( + // TODO: Replace with `sdkComponents.aapt2` when it's available in AGP + // https://issuetracker.google.com/issues/376815836 + componentsExtension.sdkComponents.sdkDirectory.map { directory -> + directory.file( + "${SdkConstants.FD_BUILD_TOOLS}/" + + "${baseExtension.buildToolsVersion}/" + + SdkConstants.FN_AAPT2, + ) + } ) - - badging = project.layout.buildDirectory.file( "outputs/apk_from_bundle/${variant.name}/${variant.name}-badging.txt", ) @@ -140,7 +140,7 @@ fun Project.configureBadgingTasks( val updateBadgingTaskName = "update${capitalizedVariantName}Badging" tasks.register(updateBadgingTaskName) { - from(generateBadging.get().badging) + from(generateBadging.map(GenerateBadgingTask::badging)) into(project.layout.projectDirectory) } @@ -148,7 +148,7 @@ fun Project.configureBadgingTasks( tasks.register(checkBadgingTaskName) { goldenBadging = project.layout.projectDirectory.file("${variant.name}-badging.txt") - generatedBadging = generateBadging.get().badging + generatedBadging.set(generateBadging.flatMap(GenerateBadgingTask::badging)) this.updateBadgingTaskName = updateBadgingTaskName diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt index 972d539c6..ed1ea4254 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt @@ -19,10 +19,12 @@ package com.google.samples.apps.nowinandroid import com.android.build.api.artifact.ScopedArtifact import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.api.variant.ScopedArtifacts +import com.android.build.api.variant.SourceDirectories import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.file.RegularFile import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Provider import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.configure @@ -88,11 +90,14 @@ internal fun Project.configureJacoco( html.required = true } - // TODO: This is missing files in src/debug/, src/prod, src/demo, src/demoDebug... + fun SourceDirectories.Flat?.toFilePaths(): Provider> = this + ?.all + ?.map { directories -> directories.map { it.asFile.path } } + ?: provider { emptyList() } sourceDirectories.setFrom( files( - "$projectDir/src/main/java", - "$projectDir/src/main/kotlin", + variant.sources.java.toFilePaths(), + variant.sources.kotlin.toFilePaths() ), ) diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index bfb799595..8a41dbfcc 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -26,8 +26,8 @@ import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.provideDelegate import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension /** * Configure base Kotlin with Android options @@ -36,7 +36,7 @@ internal fun Project.configureKotlinAndroid( commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { - compileSdk = 34 + compileSdk = 35 defaultConfig { minSdk = 21 @@ -54,7 +54,7 @@ internal fun Project.configureKotlinAndroid( configureKotlin() dependencies { - add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) + "coreLibraryDesugaring"(libs.findLibrary("android.desugarJdkLibs").get()) } } @@ -75,7 +75,7 @@ internal fun Project.configureKotlinJvm() { /** * Configure base Kotlin options */ -private inline fun Project.configureKotlin() = configure { +private inline fun Project.configureKotlin() = configure { // Treat all Kotlin warnings as errors (disabled by default) // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties val warningsAsErrors: String? by project @@ -90,5 +90,19 @@ private inline fun Project.configureKotlin // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", ) + freeCompilerArgs.add( + /** + * Remove this args after Phase 3. + * https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-consistent-copy-visibility/#deprecation-timeline + * + * Deprecation timeline + * Phase 3. (Supposedly Kotlin 2.2 or Kotlin 2.3). + * The default changes. + * Unless ExposedCopyVisibility is used, the generated 'copy' method has the same visibility as the primary constructor. + * The binary signature changes. The error on the declaration is no longer reported. + * '-Xconsistent-data-class-copy-visibility' compiler flag and ConsistentCopyVisibility annotation are now unnecessary. + */ + "-Xconsistent-data-class-copy-visibility" + ) } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt index 633098604..f57e634cc 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt @@ -16,23 +16,26 @@ enum class FlavorDimension { @Suppress("EnumEntryName") enum class NiaFlavor(val dimension: FlavorDimension, val applicationIdSuffix: String? = null) { demo(FlavorDimension.contentType, applicationIdSuffix = ".demo"), - prod(FlavorDimension.contentType) + prod(FlavorDimension.contentType), } fun configureFlavors( commonExtension: CommonExtension<*, *, *, *, *, *>, - flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {} + flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {}, ) { commonExtension.apply { - flavorDimensions += FlavorDimension.contentType.name + FlavorDimension.values().forEach { flavorDimension -> + flavorDimensions += flavorDimension.name + } + productFlavors { - NiaFlavor.values().forEach { - create(it.name) { - dimension = it.dimension.name - flavorConfigurationBlock(this, it) + NiaFlavor.values().forEach { niaFlavor -> + register(niaFlavor.name) { + dimension = niaFlavor.dimension.name + flavorConfigurationBlock(this, niaFlavor) if (this@apply is ApplicationExtension && this is ApplicationProductFlavor) { - if (it.applicationIdSuffix != null) { - applicationIdSuffix = it.applicationIdSuffix + if (niaFlavor.applicationIdSuffix != null) { + applicationIdSuffix = niaFlavor.applicationIdSuffix } } } diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties index 1c9073eb9..5e07c65d0 100644 --- a/build-logic/gradle.properties +++ b/build-logic/gradle.properties @@ -2,3 +2,5 @@ org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index de9224e22..ff96cc84a 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -14,9 +14,22 @@ * limitations under the License. */ -dependencyResolutionManagement { +pluginManagement { repositories { + gradlePluginPortal() google() + } +} + +dependencyResolutionManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() } versionCatalogs { diff --git a/build.gradle.kts b/build.gradle.kts index fbcefa906..b7989bab4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,10 +16,16 @@ buildscript { repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() - // Android Build Server + // This is used only for internal Google builds. maven { url = uri("../nowinandroid-prebuilts/m2repository") } } dependencies { diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 83c268aab..8c839fa8e 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -25,7 +25,6 @@ android { testOptions { unitTests { isIncludeAndroidResources = true - isReturnDefaultValues = true } } } diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepositoryTest.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepositoryTest.kt index d32f424d8..4050a6a39 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepositoryTest.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepositoryTest.kt @@ -67,6 +67,8 @@ class OfflineFirstTopicsRepositoryTest { @Test fun offlineFirstTopicsRepository_topics_stream_is_backed_by_topics_dao() = testScope.runTest { + subject.syncWith(synchronizer) + assertEquals( topicDao.getTopicEntities() .first() diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts index b17bf6abd..0d4ba37c5 100644 --- a/core/datastore/build.gradle.kts +++ b/core/datastore/build.gradle.kts @@ -25,11 +25,6 @@ android { consumerProguardFiles("consumer-proguard-rules.pro") } namespace = "com.google.samples.apps.nowinandroid.core.datastore" - testOptions { - unitTests { - isReturnDefaultValues = true - } - } } dependencies { diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 31635865c..aac2ddb8f 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -21,9 +21,6 @@ plugins { } android { - defaultConfig { - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } namespace = "com.google.samples.apps.nowinandroid.core.designsystem" } @@ -47,6 +44,4 @@ dependencies { testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) testImplementation(projects.core.screenshotTesting) - - androidTestImplementation(libs.bundles.androidx.compose.ui.test) } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt index 4ac19b482..4a2099dc6 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt @@ -28,10 +28,8 @@ import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.NavigationRailItemDefaults import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold @@ -184,10 +182,6 @@ fun NiaNavigationRail( * @param windowAdaptiveInfo The window adaptive info. * @param content The app content inside the scaffold. */ -@OptIn( - ExperimentalMaterial3AdaptiveNavigationSuiteApi::class, - ExperimentalMaterial3AdaptiveApi::class, -) @Composable fun NiaNavigationSuiteScaffold( navigationSuiteItems: NiaNavigationSuiteScope.() -> Unit, @@ -242,7 +236,6 @@ fun NiaNavigationSuiteScaffold( /** * A wrapper around [NavigationSuiteScope] to declare navigation items. */ -@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class) class NiaNavigationSuiteScope internal constructor( private val navigationSuiteScope: NavigationSuiteScope, private val navigationSuiteItemColors: NavigationSuiteItemColors, diff --git a/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_dynamic.png index 67cafa03d..6a9a51fe6 100644 Binary files a/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_notDynamic.png index 3f187d9d2..04de28429 100644 Binary files a/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/Background_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/Background_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/Background_light_androidTheme_notDynamic.png index ebcf62c08..758c35dce 100644 Binary files a/core/designsystem/src/test/screenshots/Background/Background_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/Background_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_dynamic.png index 7f910a34b..1b16b8fc4 100644 Binary files a/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_notDynamic.png index 912480c6a..3d802687a 100644 Binary files a/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/Background_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_dynamic.png index a9b2c8694..6e30d8b63 100644 Binary files a/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_notDynamic.png index f88a672c4..ca46d579b 100644 Binary files a/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/GradientBackground_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_androidTheme_notDynamic.png index ebcf62c08..758c35dce 100644 Binary files a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_dynamic.png index 6fef6436a..d1961cd01 100644 Binary files a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_notDynamic.png index e619f1332..a9baaaac3 100644 Binary files a/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Background/GradientBackground_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_dynamic.png index cf0656fbd..aadd4c9dc 100644 Binary files a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_notDynamic.png index 9514112f1..01de6984a 100644 Binary files a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_dynamic.png index 7774a18bc..58f94b221 100644 Binary files a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_notDynamic.png index 8858fb493..c7b6cee37 100644 Binary files a/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/ButtonLeadingIcon_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/Button_dark_androidTheme_notDynamic.png index a5d3d4a3d..0883a4497 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_dynamic.png index 01538b44b..1086161e0 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_notDynamic.png index cd0c07df1..fc12d5a7d 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/Button_light_androidTheme_notDynamic.png index ab113beec..2863a06d3 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_dynamic.png index fdbbb820d..f67bd4342 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_notDynamic.png index b567adf84..fad380492 100644 Binary files a/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/Button_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_androidTheme_notDynamic.png index a9ba099c0..a113e51bc 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_dynamic.png index 6fce27976..c1bd448e1 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_notDynamic.png index ce30b66ba..8855a359e 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_androidTheme_notDynamic.png index bb6aa592f..d6d5bd628 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_dynamic.png index c18a86878..50a6570f1 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_notDynamic.png index d2059e4d7..ea96acea3 100644 Binary files a/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Button/OutlineButton_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_androidTheme_notDynamic.png index 5881f76b7..464be9cb4 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_dynamic.png index 4b5c91914..129bd707f 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_notDynamic.png index f2e863865..54e665cf9 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_androidTheme_notDynamic.png index 1e3b04e50..0e35be51d 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_dynamic.png index 865368ca1..01e8d9690 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_notDynamic.png index be73f060d..55d3a80fa 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChipSelected_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_androidTheme_notDynamic.png index 364f59a47..568e41d78 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_dynamic.png index 8f90977fd..27c1f4195 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_notDynamic.png index 5303eb64e..5d86614f2 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_fontScale2.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_fontScale2.png index 2dc430ca8..fbd4109cd 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_fontScale2.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_fontScale2.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_androidTheme_notDynamic.png index fadd074d8..c4015dbc6 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_dynamic.png index 2f3749cf3..df97855f0 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_notDynamic.png index 0cfaaefae..28a1c895f 100644 Binary files a/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/FilterChip/FilterChip_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_androidTheme_notDynamic.png index a1512fa75..8e372685a 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_dynamic.png index fe4b54ae2..436c8d999 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_notDynamic.png index f912ce3c1..536e5cf25 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_androidTheme_notDynamic.png index 339479779..abc882504 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_dynamic.png index 92079273a..5628b5698 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_notDynamic.png index 24580adf2..f88fa1d08 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButtonUnchecked_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_androidTheme_notDynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_dynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_notDynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_androidTheme_notDynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_dynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_notDynamic.png index ce2cdf804..d217e1116 100644 Binary files a/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/IconButton/IconButton_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_1000.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_1000.png index 450d55a09..316d899d2 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_1000.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_1000.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png index 5aa1eb89a..4705b9beb 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png index 74309056f..203a2ea2e 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_724.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_724.png index 03bf6709e..50a822621 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_724.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_724.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_androidTheme_notDynamic.png index cf35893fd..d266c68f0 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_dynamic.png index 013aac763..87ae3ac55 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_notDynamic.png index 19265495c..73ad8f0fb 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_androidTheme_notDynamic.png index 156fc1983..17b56813d 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_dynamic.png index 36d79ab6c..e0b86ebb6 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_notDynamic.png index 89ea5e37c..e1b73d22b 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_androidTheme_notDynamic.png index 022ea15eb..fbfa9fd0a 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_dynamic.png index 0a7be72c2..dcd3b3eca 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_notDynamic.png index ddc43ab6a..8f1a9343d 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_androidTheme_notDynamic.png index 071ab0a04..e66b491a3 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_dynamic.png index 7170dec31..daa5ee299 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_notDynamic.png index 6829b0f78..b922f641b 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/LoadingWheel/OverlayLoadingWheel_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_androidTheme_notDynamic.png index b2a0fb99c..5e2a27c65 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_dynamic.png index 8836faebc..efb1f8756 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_notDynamic.png index a4abd2d5b..160a72d61 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_fontScale2.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_fontScale2.png index 97bbb0892..b4b4842c3 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_fontScale2.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_fontScale2.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_androidTheme_notDynamic.png index a526e36c7..c3200091b 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_dynamic.png index 5e27d2497..877c45cd0 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_notDynamic.png index f5671cb14..9fd6fc843 100644 Binary files a/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Navigation/Navigation_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tabs/Tabs_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Tabs/Tabs_dark_defaultTheme_dynamic.png index 15cb061a0..3c5fffc04 100644 Binary files a/core/designsystem/src/test/screenshots/Tabs/Tabs_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Tabs/Tabs_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tabs/Tabs_fontScale2.png b/core/designsystem/src/test/screenshots/Tabs/Tabs_fontScale2.png index f62ea3ced..d7feb863b 100644 Binary files a/core/designsystem/src/test/screenshots/Tabs/Tabs_fontScale2.png and b/core/designsystem/src/test/screenshots/Tabs/Tabs_fontScale2.png differ diff --git a/core/designsystem/src/test/screenshots/Tabs/Tabs_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Tabs/Tabs_light_defaultTheme_dynamic.png index 0564b3881..1e3270f94 100644 Binary files a/core/designsystem/src/test/screenshots/Tabs/Tabs_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Tabs/Tabs_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_dark_androidTheme_notDynamic.png index 522dcd301..2a065eb57 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_dynamic.png index 13345c365..c532a307e 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_notDynamic.png index d6cfb48d0..61d3321b8 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_fontScale2.png b/core/designsystem/src/test/screenshots/Tag/Tag_fontScale2.png index 475707556..7cfdbb2a7 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_fontScale2.png and b/core/designsystem/src/test/screenshots/Tag/Tag_fontScale2.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_light_androidTheme_notDynamic.png index 38ebe8b42..fc29b6e11 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_dynamic.png index 00144ba15..ed825ba71 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_notDynamic.png index 53b1da266..8acc6b0a8 100644 Binary files a/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/Tag/Tag_light_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_androidTheme_notDynamic.png index 753c13605..3617feab1 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_dynamic.png index 1c2d9b3ec..418e0f88d 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_notDynamic.png index 1baa2362c..921698d22 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_dark_defaultTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_fontScale2.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_fontScale2.png index 234304db1..497561ef7 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_fontScale2.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_fontScale2.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_androidTheme_notDynamic.png index 7c0348b04..4988252e5 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_androidTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_androidTheme_notDynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_dynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_dynamic.png index fbf61adc4..6ab40ebf9 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_dynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_dynamic.png differ diff --git a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_notDynamic.png b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_notDynamic.png index 078378bea..d9d014a18 100644 Binary files a/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_notDynamic.png and b/core/designsystem/src/test/screenshots/TopAppBar/TopAppBar_light_defaultTheme_notDynamic.png differ diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt index 77dfa4394..a56bbcb8d 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt @@ -22,7 +22,6 @@ import kotlinx.datetime.Instant * A [NewsResource] with additional user information such as whether the user is following the * news resource's topics and whether they have saved (bookmarked) this news resource. */ -@ConsistentCopyVisibility data class UserNewsResource internal constructor( val id: String, val title: String, diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/demo/DemoNiaNetworkDataSource.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/demo/DemoNiaNetworkDataSource.kt index b7c912c00..328cc4e0f 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/demo/DemoNiaNetworkDataSource.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/demo/DemoNiaNetworkDataSource.kt @@ -17,6 +17,8 @@ package com.google.samples.apps.nowinandroid.core.network.demo import JvmUnitTestDemoAssetManager +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES.M import com.google.samples.apps.nowinandroid.core.network.Dispatcher import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource @@ -28,6 +30,7 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream +import java.io.BufferedReader import javax.inject.Inject /** @@ -39,17 +42,11 @@ class DemoNiaNetworkDataSource @Inject constructor( private val assets: DemoAssetManager = JvmUnitTestDemoAssetManager, ) : NiaNetworkDataSource { - @OptIn(ExperimentalSerializationApi::class) override suspend fun getTopics(ids: List?): List = - withContext(ioDispatcher) { - assets.open(TOPICS_ASSET).use(networkJson::decodeFromStream) - } + getDataFromJsonFile(TOPICS_ASSET) - @OptIn(ExperimentalSerializationApi::class) override suspend fun getNewsResources(ids: List?): List = - withContext(ioDispatcher) { - assets.open(NEWS_ASSET).use(networkJson::decodeFromStream) - } + getDataFromJsonFile(NEWS_ASSET) override suspend fun getTopicChangeList(after: Int?): List = getTopics().mapToChangeList(NetworkTopic::id) @@ -57,6 +54,27 @@ class DemoNiaNetworkDataSource @Inject constructor( override suspend fun getNewsResourceChangeList(after: Int?): List = getNewsResources().mapToChangeList(NetworkNewsResource::id) + /** + * Get data from the given JSON [fileName]. + */ + @OptIn(ExperimentalSerializationApi::class) + private suspend inline fun getDataFromJsonFile(fileName: String): List = + withContext(ioDispatcher) { + assets.open(fileName).use { inputStream -> + if (SDK_INT <= M) { + /** + * On API 23 (M) and below we must use a workaround to avoid an exception being + * thrown during deserialization. See: + * https://github.com/Kotlin/kotlinx.serialization/issues/2457#issuecomment-1786923342 + */ + inputStream.bufferedReader().use(BufferedReader::readText) + .let(networkJson::decodeFromString) + } else { + networkJson.decodeFromStream(inputStream) + } + } + } + companion object { private const val NEWS_ASSET = "news.json" private const val TOPICS_ASSET = "topics.json" diff --git a/core/screenshot-testing/build.gradle.kts b/core/screenshot-testing/build.gradle.kts index 794416ba9..57a43a200 100644 --- a/core/screenshot-testing/build.gradle.kts +++ b/core/screenshot-testing/build.gradle.kts @@ -26,6 +26,7 @@ android { dependencies { api(libs.bundles.androidx.compose.ui.test) api(libs.roborazzi) + api(libs.roborazzi.accessibility.check) implementation(libs.androidx.compose.ui.test) implementation(libs.androidx.activity.compose) implementation(libs.robolectric) diff --git a/core/screenshot-testing/src/main/AndroidManifest.xml b/core/screenshot-testing/src/main/AndroidManifest.xml index 51d0cfc2e..5952f8bfd 100644 --- a/core/screenshot-testing/src/main/AndroidManifest.xml +++ b/core/screenshot-testing/src/main/AndroidManifest.xml @@ -14,4 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file + + + + \ No newline at end of file diff --git a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index f11651220..20b9a5deb 100644 --- a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -14,8 +14,11 @@ * limitations under the License. */ +@file:OptIn(ExperimentalRoborazziApi::class) + package com.google.samples.apps.nowinandroid.core.testing.util +import android.graphics.Bitmap.CompressFormat.PNG import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable @@ -30,12 +33,25 @@ import androidx.compose.ui.test.DeviceConfigurationOverride import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.github.takahirom.roborazzi.ExperimentalRoborazziApi +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityCheckOptions +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker.CheckLevel import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions import com.github.takahirom.roborazzi.captureRoboImage +import com.github.takahirom.roborazzi.checkRoboAccessibility +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult +import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException +import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme +import org.hamcrest.Matcher +import org.hamcrest.Matchers import org.robolectric.RuntimeEnvironment +import java.io.File +import java.io.FileOutputStream val DefaultRoborazziOptions = RoborazziOptions( @@ -52,10 +68,17 @@ enum class DefaultTestDevices(val description: String, val spec: String) { } fun AndroidComposeTestRule, A>.captureMultiDevice( screenshotName: String, + accessibilitySuppressions: Matcher = Matchers.not(Matchers.anything()), body: @Composable () -> Unit, ) { DefaultTestDevices.entries.forEach { - this.captureForDevice(it.description, it.spec, screenshotName, body = body) + this.captureForDevice( + deviceName = it.description, + deviceSpec = it.spec, + screenshotName = screenshotName, + body = body, + accessibilitySuppressions = accessibilitySuppressions, + ) } } @@ -64,6 +87,7 @@ fun AndroidComposeTestRule, A>.c deviceSpec: String, screenshotName: String, roborazziOptions: RoborazziOptions = DefaultRoborazziOptions, + accessibilitySuppressions: Matcher = Matchers.not(Matchers.anything()), darkMode: Boolean = false, body: @Composable () -> Unit, ) { @@ -83,11 +107,46 @@ fun AndroidComposeTestRule, A>.c } } } + + // Run Accessibility checks first so logging is included + val accessibilityException = try { + this.onRoot().checkRoboAccessibility( + roborazziATFAccessibilityCheckOptions = RoborazziATFAccessibilityCheckOptions( + failureLevel = CheckLevel.Error, + checker = RoborazziATFAccessibilityChecker( + preset = AccessibilityCheckPreset.LATEST, + suppressions = accessibilitySuppressions, + ), + ), + ) + null + } catch (e: AccessibilityViewCheckException) { + e + } + this.onRoot() .captureRoboImage( "src/test/screenshots/${screenshotName}_$deviceName.png", roborazziOptions = roborazziOptions, ) + + // Rethrow the Accessibility exception once screenshots have passed + if (accessibilityException != null) { + accessibilityException.results.forEachIndexed { index, check -> + val viewImage = check.viewImage + if (viewImage is BitmapImage) { + val file = File("build/outputs/roborazzi/${screenshotName}_${deviceName}_$index.png") + println("Writing check.viewImage to $file") + FileOutputStream( + file, + ).use { + viewImage.bitmap.compress(PNG, 100, it) + } + } + } + + throw accessibilityException + } } /** diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index c22a02fa1..0b7b9f570 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -21,7 +21,6 @@ import android.net.Uri import androidx.annotation.ColorInt import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope @@ -45,7 +44,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource * An extension on [LazyListScope] defining a feed with news resources. * Depending on the [feedState], this might emit no items. */ -@OptIn(ExperimentalFoundationApi::class) fun LazyStaggeredGridScope.newsFeed( feedState: NewsFeedUiState, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, diff --git a/docs/images/screenshot-1-foryou.png b/docs/images/screenshot-1-foryou.png deleted file mode 100644 index 0d76fce79..000000000 Binary files a/docs/images/screenshot-1-foryou.png and /dev/null differ diff --git a/docs/images/screenshot-2-interests.png b/docs/images/screenshot-2-interests.png deleted file mode 100644 index 1137e59c3..000000000 Binary files a/docs/images/screenshot-2-interests.png and /dev/null differ diff --git a/docs/images/screenshot-3-topicdetail.png b/docs/images/screenshot-3-topicdetail.png deleted file mode 100644 index d5150d376..000000000 Binary files a/docs/images/screenshot-3-topicdetail.png and /dev/null differ diff --git a/docs/images/screenshots.png b/docs/images/screenshots.png index f92fe6455..3f143bc6c 100644 Binary files a/docs/images/screenshots.png and b/docs/images/screenshots.png differ diff --git a/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt b/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt index 40f54e4a7..dd01b84c9 100644 --- a/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt +++ b/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt @@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.filter @@ -33,6 +32,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState diff --git a/feature/bookmarks/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModelTest.kt b/feature/bookmarks/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModelTest.kt index 19ab0ad85..aa42adae2 100644 --- a/feature/bookmarks/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModelTest.kt +++ b/feature/bookmarks/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksViewModelTest.kt @@ -31,7 +31,9 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertIs +import kotlin.test.assertTrue /** * To learn more about how this test handles Flows created with stateIn, see @@ -86,5 +88,49 @@ class BookmarksViewModelTest { val item = viewModel.feedUiState.value assertIs(item) assertEquals(item.feed.size, 0) + assertTrue(viewModel.shouldDisplayUndoBookmark) + } + + @Test + fun feedUiState_resourceIsViewed_setResourcesViewed() = runTest { + backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() } + + // Given + newsRepository.sendNewsResources(newsResourcesTestData) + userDataRepository.setNewsResourceBookmarked(newsResourcesTestData[0].id, true) + val itemBeforeViewed = viewModel.feedUiState.value + assertIs(itemBeforeViewed) + assertFalse(itemBeforeViewed.feed.first().hasBeenViewed) + + // When + viewModel.setNewsResourceViewed(newsResourcesTestData[0].id, true) + + // Then + val item = viewModel.feedUiState.value + assertIs(item) + assertTrue(item.feed.first().hasBeenViewed) + } + + @Test + fun feedUiState_undoneBookmarkRemoval_bookmarkIsRestored() = runTest { + backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.feedUiState.collect() } + + // Given + newsRepository.sendNewsResources(newsResourcesTestData) + userDataRepository.setNewsResourceBookmarked(newsResourcesTestData[0].id, true) + viewModel.removeFromSavedResources(newsResourcesTestData[0].id) + assertTrue(viewModel.shouldDisplayUndoBookmark) + val itemBeforeUndo = viewModel.feedUiState.value + assertIs(itemBeforeUndo) + assertEquals(0, itemBeforeUndo.feed.size) + + // When + viewModel.undoBookmarkRemoval() + + // Then + assertFalse(viewModel.shouldDisplayUndoBookmark) + val item = viewModel.feedUiState.value + assertIs(item) + assertEquals(1, item.feed.size) } } diff --git a/feature/foryou/build.gradle.kts b/feature/foryou/build.gradle.kts index 41d5b16a2..59f6844cf 100644 --- a/feature/foryou/build.gradle.kts +++ b/feature/foryou/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { implementation(libs.accompanist.permissions) implementation(projects.core.data) implementation(projects.core.domain) - implementation(project(":core:notifications")) + implementation(projects.core.notifications) testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) diff --git a/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt b/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt index 14b67c64e..29fc6f536 100644 --- a/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt +++ b/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt @@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.feature.foryou import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.createAndroidComposeRule +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesElements +import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck +import com.google.android.apps.common.testing.accessibility.framework.matcher.ElementMatchers.withText import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.testing.util.DefaultTestDevices @@ -31,6 +35,7 @@ import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Loa import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.NotShown import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Shown import dagger.hilt.android.testing.HiltTestApplication +import org.hamcrest.Matchers import org.junit.Before import org.junit.Rule import org.junit.Test @@ -108,7 +113,20 @@ class ForYouScreenScreenshotTests { @Test fun forYouScreenTopicSelection() { - composeTestRule.captureMultiDevice("ForYouScreenTopicSelection") { + composeTestRule.captureMultiDevice( + "ForYouScreenTopicSelection", + accessibilitySuppressions = Matchers.allOf( + AccessibilityCheckResultUtils.matchesCheck(TextContrastCheck::class.java), + Matchers.anyOf( + // Disabled Button + matchesElements(withText("Done")), + + // TODO investigate, seems a false positive + matchesElements(withText("What are you interested in?")), + matchesElements(withText("UI")), + ), + ), + ) { ForYouScreenTopicSelection() } } diff --git a/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png index f362c445d..a699345c2 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenLoading_phone.png b/feature/foryou/src/test/screenshots/ForYouScreenLoading_phone.png index 8d02e5985..30c8fdad7 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenLoading_phone.png and b/feature/foryou/src/test/screenshots/ForYouScreenLoading_phone.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenLoading_tablet.png b/feature/foryou/src/test/screenshots/ForYouScreenLoading_tablet.png index e6f6a527a..f54966dbc 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenLoading_tablet.png and b/feature/foryou/src/test/screenshots/ForYouScreenLoading_tablet.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png index f5ca39c3a..3990aee3f 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_foldable.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone.png index 7a3f99d7c..a86a8232f 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone_dark.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone_dark.png index 3a14048b5..d500394ef 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone_dark.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_phone_dark.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_tablet.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_tablet.png index 97458f73b..3b735462f 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_tablet.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedAndLoading_tablet.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png index 0b539aeca..0da7e19ac 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_phone.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_phone.png index b19c8d708..a7b775315 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_phone.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_phone.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_tablet.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_tablet.png index bdf44b2a3..e7f0cc050 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_tablet.png and b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_tablet.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_foldable.png index b095c1a7a..f5d68fce2 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_foldable.png and b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_foldable.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone.png b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone.png index 140fa8d6d..b04c632c3 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone.png and b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone_dark.png b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone_dark.png index 5d90732a0..6c4d66b28 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone_dark.png and b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_phone_dark.png differ diff --git a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_tablet.png b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_tablet.png index 3dd62e765..f7c20a890 100644 Binary files a/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_tablet.png and b/feature/foryou/src/test/screenshots/ForYouScreenTopicSelection_tablet.png differ diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt index ff91941a8..b617f98a9 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt @@ -522,6 +522,7 @@ private fun SearchTextField( .focusRequester(focusRequester) .onKeyEvent { if (it.key == Key.Enter) { + if (searchQuery.isBlank()) return@onKeyEvent false onSearchExplicitlyTriggered() true } else { @@ -536,6 +537,7 @@ private fun SearchTextField( ), keyboardActions = KeyboardActions( onSearch = { + if (searchQuery.isBlank()) return@KeyboardActions onSearchExplicitlyTriggered() }, ), diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt index 6c2af240c..36947880e 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt @@ -59,7 +59,7 @@ class SearchViewModel @Inject constructor( flowOf(SearchResultUiState.SearchNotReady) } else { searchQuery.flatMapLatest { query -> - if (query.length < SEARCH_QUERY_MIN_LENGTH) { + if (query.trim().length < SEARCH_QUERY_MIN_LENGTH) { flowOf(SearchResultUiState.EmptyQuery) } else { getSearchContentsUseCase(query) @@ -102,6 +102,7 @@ class SearchViewModel @Inject constructor( * search query in the search text field, defining this method. */ fun onSearchTriggered(query: String) { + if (query.isBlank()) return viewModelScope.launch { recentSearchRepository.insertOrReplaceRecentSearch(searchQuery = query) } diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt index 81f3576b4..3b16e5f71 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt @@ -21,10 +21,12 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.search.SearchRoute +import kotlinx.serialization.Serializable -const val SEARCH_ROUTE = "search_route" +@Serializable data object SearchRoute -fun NavController.navigateToSearch(navOptions: NavOptions? = null) = navigate(SEARCH_ROUTE, navOptions) +fun NavController.navigateToSearch(navOptions: NavOptions? = null) = + navigate(SearchRoute, navOptions) fun NavGraphBuilder.searchScreen( onBackClick: () -> Unit, @@ -33,7 +35,7 @@ fun NavGraphBuilder.searchScreen( ) { // TODO: Handle back stack for each top-level destination. At the moment each top-level // destination may have own search screen's back stack. - composable(route = SEARCH_ROUTE) { + composable { SearchRoute( onBackClick = onBackClick, onInterestsClick = onInterestsClick, diff --git a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt index a62965b52..1b866cec2 100644 --- a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt +++ b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt @@ -41,6 +41,7 @@ import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNull /** * To learn more about how this test handles Flows created with stateIn, see @@ -122,6 +123,43 @@ class SearchViewModelTest { assertEquals(SearchNotReady, viewModel.searchResultUiState.value) } + @Test + fun emptySearchText_isNotAddedToRecentSearches() = runTest { + viewModel.onSearchTriggered("") + + val recentSearchQueriesStream = getRecentQueryUseCase() + val recentSearchQueries = recentSearchQueriesStream.first() + val recentSearchQuery = recentSearchQueries.firstOrNull() + + assertNull(recentSearchQuery) + } + + @Test + fun searchTextWithThreeSpaces_isEmptyQuery() = runTest { + searchContentsRepository.addNewsResources(newsResourcesTestData) + searchContentsRepository.addTopics(topicsTestData) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + + viewModel.onSearchQueryChanged(" ") + + assertIs(viewModel.searchResultUiState.value) + + collectJob.cancel() + } + + @Test + fun searchTextWithThreeSpacesAndOneLetter_isEmptyQuery() = runTest { + searchContentsRepository.addNewsResources(newsResourcesTestData) + searchContentsRepository.addTopics(topicsTestData) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + + viewModel.onSearchQueryChanged(" a") + + assertIs(viewModel.searchResultUiState.value) + + collectJob.cancel() + } + @Test fun whenToggleNewsResourceSavedIsCalled_bookmarkStateIsUpdated() = runTest { val newsResourceId = "123" diff --git a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index db60a6447..ad7f30f43 100644 --- a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -20,7 +20,6 @@ package com.google.samples.apps.nowinandroid.feature.settings import android.content.Intent import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -141,14 +140,16 @@ fun SettingsDialog( TrackScreenViewEvent(screenName = "Settings") }, confirmButton = { - Text( - text = stringResource(string.feature_settings_dismiss_dialog_button_text), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .padding(horizontal = 8.dp) - .clickable { onDismiss() }, - ) + NiaTextButton( + onClick = onDismiss, + modifier = Modifier.padding(horizontal = 8.dp), + ) { + Text( + text = stringResource(string.feature_settings_dismiss_dialog_button_text), + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, + ) + } }, ) } diff --git a/generateModuleGraphs.sh b/generateModuleGraphs.sh index 3c3583e67..5307d2932 100755 --- a/generateModuleGraphs.sh +++ b/generateModuleGraphs.sh @@ -27,7 +27,8 @@ then echo "The 'dot' command is not found. This is required to generate SVGs from the Graphviz files." echo "Installation instructions:" echo " - On macOS: You can install Graphviz using Homebrew with the command: 'brew install graphviz'" - echo " - On Ubuntu: You can install Graphviz using APT with the command: 'sudo apt-get install graphviz'" + echo " - On Ubuntu: You can install Graphviz using APT with the command: 'sudo apt install graphviz'" + echo " - Others: Visit https://graphviz.org/download/" exit 1 fi diff --git a/gradle.properties b/gradle.properties index e2d6e0903..2e9d9fc30 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,7 @@ org.gradle.caching=true # Enable configuration caching between builds. org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true # This option is set because of https://github.com/google/play-services-plugins/issues/246 # to generate the Configuration Cache regardless of incompatible tasks. # See https://github.com/android/nowinandroid/issues/1022 before using it. @@ -58,3 +59,7 @@ android.defaults.buildfeatures.shaders=false # Run Roborazzi screenshot tests with the local tests roborazzi.test.verify=true + +# Prevent uninstall app after instrumented tests +# https://issuetracker.google.com/issues/295039976 +android.injected.androidTest.leaveApksInstalledAfterRun=true diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index fe79fa01e..44dc41200 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -14,10 +14,10 @@ * limitations under the License. */ -val ktlintVersion = "1.0.1" +val ktlintVersion = "1.4.0" initscript { - val spotlessVersion = "6.23.3" + val spotlessVersion = "6.25.0" repositories { mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 61ac0ee05..ce4bd2c64 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,24 +1,25 @@ [versions] -accompanist = "0.34.0" -androidDesugarJdkLibs = "2.0.4" +accompanist = "0.37.0" +androidDesugarJdkLibs = "2.1.4" # AGP and tools should be updated together -androidGradlePlugin = "8.7.2" -androidTools = "31.7.2" +androidGradlePlugin = "8.7.3" +androidTools = "31.7.3" androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" -androidxComposeBom = "2024.11.00" -androidxComposeRuntimeTracing = "1.0.0-beta01" -androidxCore = "1.13.1" +androidxComposeBom = "2024.12.01" +androidxComposeRuntimeTracing = "1.7.6" +androidxCore = "1.15.0" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.1.1" androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" -androidxLifecycle = "2.8.6" -androidxMacroBenchmark = "1.3.0" +androidxLifecycle = "2.8.7" +androidxLintGradle = "1.0.0-alpha03" +androidxMacroBenchmark = "1.3.3" androidxMetrics = "1.0.0-beta01" -androidxNavigation = "2.8.0" -androidxProfileinstaller = "1.3.1" +androidxNavigation = "2.8.5" +androidxProfileinstaller = "1.4.1" androidxTestCore = "1.6.1" androidxTestExt = "1.2.1" androidxTestRules = "1.6.1" @@ -26,36 +27,36 @@ androidxTestRunner = "1.6.2" androidxTracing = "1.3.0-alpha02" androidxUiAutomator = "2.3.0" androidxWindowManager = "1.3.0" -androidxWork = "2.9.0" +androidxWork = "2.10.0" coil = "2.7.0" dependencyGuard = "0.5.0" -firebaseBom = "33.3.0" -firebaseCrashlyticsPlugin = "2.9.9" +firebaseBom = "33.7.0" +firebaseCrashlyticsPlugin = "3.0.2" firebasePerfPlugin = "1.4.2" -gmsPlugin = "4.4.1" +gmsPlugin = "4.4.2" googleOss = "17.1.0" googleOssPlugin = "0.10.6" -hilt = "2.52" +hilt = "2.54" hiltExt = "1.2.0" -jacoco = "0.8.7" +jacoco = "0.8.12" junit4 = "4.13.2" -kotlin = "2.0.20" -kotlinxCoroutines = "1.9.0" +kotlin = "2.1.0" +kotlinxCoroutines = "1.10.1" kotlinxDatetime = "0.6.1" -kotlinxSerializationJson = "1.6.3" -ksp = "2.0.20-1.0.25" +kotlinxSerializationJson = "1.8.0" +ksp = "2.1.0-1.0.29" moduleGraph = "2.7.1" okhttp = "4.12.0" -protobuf = "4.26.1" +protobuf = "4.29.2" protobufPlugin = "0.9.4" retrofit = "2.11.0" retrofitKotlinxSerializationJson = "1.0.0" -robolectric = "4.12.2" -roborazzi = "1.7.0" +robolectric = "4.14.1" +roborazzi = "1.39.0" room = "2.6.1" secrets = "2.0.1" -truth = "1.4.2" -turbine = "1.1.0" +truth = "1.4.4" +turbine = "1.2.0" [bundles] androidx-compose-ui-test = ["androidx-compose-ui-test", "androidx-compose-ui-testManifest"] @@ -92,6 +93,7 @@ androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navig androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" } androidx-lifecycle-runtimeTesting = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" } androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } +androidx-lint-gradle = { group = "androidx.lint", name = "lint-gradle", version.ref = "androidxLintGradle" } androidx-metrics = { group = "androidx.metrics", name = "metrics-performance", version.ref = "androidxMetrics" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" } androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" } @@ -141,6 +143,7 @@ retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.r retrofit-kotlin-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" } robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" } +roborazzi-accessibility-check = { group = "io.github.takahirom.roborazzi", name = "roborazzi-accessibility-check", version.ref = "roborazzi" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } @@ -160,6 +163,7 @@ room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", vers [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" } android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } baselineprofile = { id = "androidx.baselineprofile", version.ref = "androidxMacroBenchmark"} compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b4155..cea7a793a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/lint/build.gradle.kts b/lint/build.gradle.kts index 99a057362..f1722fa78 100644 --- a/lint/build.gradle.kts +++ b/lint/build.gradle.kts @@ -38,7 +38,7 @@ kotlin { dependencies { compileOnly(libs.kotlin.stdlib) compileOnly(libs.lint.api) + testImplementation(libs.kotlin.test) testImplementation(libs.lint.checks) testImplementation(libs.lint.tests) - testImplementation(kotlin("test")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 465a72616..2b8c6e45c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,7 +17,13 @@ pluginManagement { includeBuild("build-logic") repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() gradlePluginPortal() } @@ -26,7 +32,13 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() } } diff --git a/sync/work/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorkerTest.kt b/sync/work/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorkerTest.kt index 9c9d13510..481875e69 100644 --- a/sync/work/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorkerTest.kt +++ b/sync/work/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorkerTest.kt @@ -64,12 +64,12 @@ class SyncWorkerTest { val preRunWorkInfo = workManager.getWorkInfoById(request.id).get() // Assert - assertEquals(WorkInfo.State.ENQUEUED, preRunWorkInfo.state) + assertEquals(WorkInfo.State.ENQUEUED, preRunWorkInfo?.state) // Tells the testing framework that the constraints have been met testDriver.setAllConstraintsMet(request.id) val postRequirementWorkInfo = workManager.getWorkInfoById(request.id).get() - assertEquals(WorkInfo.State.RUNNING, postRequirementWorkInfo.state) + assertEquals(WorkInfo.State.RUNNING, postRequirementWorkInfo?.state) } } diff --git a/ui-test-hilt-manifest/src/main/AndroidManifest.xml b/ui-test-hilt-manifest/src/main/AndroidManifest.xml index 8fbb1c299..d35bfe1e2 100644 --- a/ui-test-hilt-manifest/src/main/AndroidManifest.xml +++ b/ui-test-hilt-manifest/src/main/AndroidManifest.xml @@ -17,7 +17,14 @@ - + + \ No newline at end of file