diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index c243e1138..716449e43 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -1,6 +1,7 @@ name: Build on: + workflow_dispatch: push: branches: - main @@ -29,16 +30,19 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-use-agree: "yes" - name: Check build-logic run: ./gradlew :build-logic:convention:check @@ -139,11 +143,26 @@ jobs: name: lint-reports path: '**/build/reports/lint-results-*.html' - - name: Upload lint reports (SARIF) - if: ${{ !cancelled() && hashFiles('**/*.sarif') != '' }} + - name: Upload lint reports (SARIF) for app module + if: ${{ !cancelled() && hashFiles('app/**/*.sarif') != '' }} uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: './' + sarif_file: './app/' + category: app + + - name: Upload lint reports (SARIF) for app-nia-catalog module + if: ${{ !cancelled() && hashFiles('app-nia-catalog/**/*.sarif') != '' }} + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: './app-nia-catalog/' + category: app-nia-catalog + + - name: Upload lint reports (SARIF) for lint module + if: ${{ !cancelled() && hashFiles('lint/**/*.sarif') != '' }} + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: './lint/' + category: lint - name: Check badging run: ./gradlew :app:checkProdReleaseBadging @@ -180,16 +199,19 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-use-agree: "yes" - name: Build projects and run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 diff --git a/.github/workflows/NightlyBaselineProfiles.yaml b/.github/workflows/NightlyBaselineProfiles.yaml index 6e7354476..43d4b73fa 100644 --- a/.github/workflows/NightlyBaselineProfiles.yaml +++ b/.github/workflows/NightlyBaselineProfiles.yaml @@ -1,6 +1,7 @@ name: NightlyBaselineProfiles on: + workflow_dispatch: schedule: - cron: '42 4 * * *' @@ -39,6 +40,9 @@ jobs: uses: gradle/actions/setup-gradle@v4 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-use-agree: "yes" - name: Setup Android SDK uses: android-actions/setup-android@v3 diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index b952ccb50..4c764a51d 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -1,6 +1,7 @@ name: GitHub Release with APKs on: + workflow_dispatch: push: tags: - 'v*' @@ -36,6 +37,9 @@ jobs: uses: gradle/actions/setup-gradle@v4 with: cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/terms-of-service" + build-scan-terms-of-use-agree: "yes" - name: Setup Android SDK uses: android-actions/setup-android@v3 @@ -75,4 +79,4 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: app/build/outputs/apk/demo/release/app-demo-release.apk asset_name: app-demo-release.apk - asset_content_type: application/vnd.android.package-archive + asset_content_type: application/vnd.android.package-archive diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 000000000..95ef6e64e --- /dev/null +++ b/AGENT.md @@ -0,0 +1,57 @@ +# Now in Android Project + +Now in Android is a native Android mobile application written in Kotlin. It provides regular news +about Android development. Users can choose to follow topics, be notified when new content is +available, and bookmark items. + +## Architecture + +This project is a modern Android application that follows the official architecture guidance from Google. It is a reactive, single-activity app that uses the following: + +- **UI:** Built entirely with Jetpack Compose, including Material 3 components and adaptive layouts for different screen sizes. +- **State Management:** Unidirectional Data Flow (UDF) is implemented using Kotlin Coroutines and Flows. ViewModels act as state holders, exposing UI state as streams of data. +- **Dependency Injection:** Hilt is used for dependency injection throughout the app, simplifying the management of dependencies and improving testability. +- **Navigation:** Navigation is handled by Jetpack Navigation 2 for Compose, allowing for a declarative and type-safe way to navigate between screens. +- **Data:** The data layer is implemented using the repository pattern. + - **Local Data:** Room and DataStore are used for local data persistence. + - **Remote Data:** Retrofit and OkHttp are used for fetching data from the network. +- **Background Processing:** WorkManager is used for deferrable background tasks. + +## Modules +The main Android app lives in the `app/` folder. Feature modules live in "feature/" and core and shared modules in "core/". + +## Commands to Build & Test + +The app and Android libraries have two product flavors: `demo` and `prod`. + +- Build: `./gradlew assemble{Variant}`. Typically `assembleDemoDebug`. +- Fix linting/formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply` +- Run local tests: `./gradlew {variant}Test` +- Run single test: `./gradlew {variant}Test --tests "com.example.myapp.MyTestClass"` +- Run local screenshot tests: `./gradlew verifyRoborazziDemoDebug` + +### Instrumented tests + +- Gradle-managed devices to run on device tests: `./gradlew pixel6api31aospDebugAndroidTest`. Also `pixel4api30aospatdDebugAndroidTest` and `pixelcapi30aospatdDebugAndroidTest`. + +### Creating tests + +#### Instrumented tests + +- Tests for UI features should only use ComposeTestRule with a ComponentActivity. +- Bigger tests live in :app and they can start activities like MainActivity. + +#### Local tests + +- Kotlinx Coroutines for most assertions +- Turbine for complex coroutine tests +- Truth for assertions + +## Continuous integration + +- The workflows are defined in `.github/workflows/*.yaml` and they contain various checks. +- Screenshot tests are generated by CI, so they shouldn't be checked into the repo from a workstation. + +## Version control and code location + +- The project uses git and is hosted in github.com/android/nowinandroid. \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..db0882010 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @dturner diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 04a864e86..587e4feba 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -9,48 +9,48 @@ 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.5.0-beta01 -androidx.collection:collection-ktx:1.5.0-beta01 -androidx.collection:collection:1.5.0-beta01 -androidx.compose.animation:animation-android:1.8.0-alpha07 -androidx.compose.animation:animation-core-android:1.8.0-alpha07 -androidx.compose.animation:animation-core:1.8.0-alpha07 -androidx.compose.animation:animation:1.8.0-alpha07 -androidx.compose.foundation:foundation-android:1.8.0-alpha07 -androidx.compose.foundation:foundation-layout-android:1.8.0-alpha07 -androidx.compose.foundation:foundation-layout:1.8.0-alpha07 -androidx.compose.foundation:foundation:1.8.0-alpha07 -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.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.8.0-alpha07 -androidx.compose.runtime:runtime-saveable-android:1.8.0-alpha07 -androidx.compose.runtime:runtime-saveable:1.8.0-alpha07 -androidx.compose.runtime:runtime:1.8.0-alpha07 -androidx.compose.ui:ui-android:1.8.0-alpha07 -androidx.compose.ui:ui-geometry-android:1.8.0-alpha07 -androidx.compose.ui:ui-geometry:1.8.0-alpha07 -androidx.compose.ui:ui-graphics-android:1.8.0-alpha07 -androidx.compose.ui:ui-graphics:1.8.0-alpha07 -androidx.compose.ui:ui-text-android:1.8.0-alpha07 -androidx.compose.ui:ui-text:1.8.0-alpha07 -androidx.compose.ui:ui-tooling-preview-android:1.8.0-alpha07 -androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07 -androidx.compose.ui:ui-unit-android:1.8.0-alpha07 -androidx.compose.ui:ui-unit:1.8.0-alpha07 -androidx.compose.ui:ui-util-android:1.8.0-alpha07 -androidx.compose.ui:ui-util:1.8.0-alpha07 -androidx.compose.ui:ui:1.8.0-alpha07 -androidx.compose:compose-bom:2024.12.01 +androidx.collection:collection-jvm:1.5.0-beta03 +androidx.collection:collection-ktx:1.5.0-beta03 +androidx.collection:collection:1.5.0-beta03 +androidx.compose.animation:animation-android:1.8.0-beta02 +androidx.compose.animation:animation-core-android:1.8.0-beta02 +androidx.compose.animation:animation-core:1.8.0-beta02 +androidx.compose.animation:animation:1.8.0-beta02 +androidx.compose.foundation:foundation-android:1.8.0-beta02 +androidx.compose.foundation:foundation-layout-android:1.8.0-beta02 +androidx.compose.foundation:foundation-layout:1.8.0-beta02 +androidx.compose.foundation:foundation:1.8.0-beta02 +androidx.compose.material3.adaptive:adaptive-android:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive:1.1.0-rc01 +androidx.compose.material3:material3-adaptive-navigation-suite-android:1.4.0-alpha08 +androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha08 +androidx.compose.material3:material3-android:1.4.0-alpha08 +androidx.compose.material3:material3:1.4.0-alpha08 +androidx.compose.material:material-icons-core-android:1.7.8 +androidx.compose.material:material-icons-core:1.7.8 +androidx.compose.material:material-icons-extended-android:1.7.8 +androidx.compose.material:material-icons-extended:1.7.8 +androidx.compose.material:material-ripple-android:1.8.0-beta02 +androidx.compose.material:material-ripple:1.8.0-beta02 +androidx.compose.runtime:runtime-android:1.8.0-beta02 +androidx.compose.runtime:runtime-saveable-android:1.8.0-beta02 +androidx.compose.runtime:runtime-saveable:1.8.0-beta02 +androidx.compose.runtime:runtime:1.8.0-beta02 +androidx.compose.ui:ui-android:1.8.0-beta02 +androidx.compose.ui:ui-geometry-android:1.8.0-beta02 +androidx.compose.ui:ui-geometry:1.8.0-beta02 +androidx.compose.ui:ui-graphics-android:1.8.0-beta02 +androidx.compose.ui:ui-graphics:1.8.0-beta02 +androidx.compose.ui:ui-text-android:1.8.0-beta02 +androidx.compose.ui:ui-text:1.8.0-beta02 +androidx.compose.ui:ui-tooling-preview-android:1.8.0-beta02 +androidx.compose.ui:ui-tooling-preview:1.8.0-beta02 +androidx.compose.ui:ui-unit-android:1.8.0-beta02 +androidx.compose.ui:ui-unit:1.8.0-beta02 +androidx.compose.ui:ui-util-android:1.8.0-beta02 +androidx.compose.ui:ui-util:1.8.0-beta02 +androidx.compose.ui:ui:1.8.0-beta02 +androidx.compose:compose-bom-alpha:2025.02.00 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.13.1 androidx.core:core:1.13.1 @@ -60,6 +60,8 @@ androidx.emoji2:emoji2:1.4.0 androidx.exifinterface:exifinterface:1.3.7 androidx.fragment:fragment:1.5.1 androidx.graphics:graphics-path:1.0.1 +androidx.graphics:graphics-shapes-android:1.0.1 +androidx.graphics:graphics-shapes:1.0.1 androidx.interpolator:interpolator:1.0.0 androidx.lifecycle:lifecycle-common-java8:2.8.7 androidx.lifecycle:lifecycle-common-jvm:2.8.7 @@ -96,10 +98,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.54 -com.google.dagger:dagger:2.54 -com.google.dagger:hilt-android:2.54 -com.google.dagger:hilt-core:2.54 +com.google.dagger:dagger-lint-aar:2.56 +com.google.dagger:dagger:2.56 +com.google.dagger:hilt-android:2.56 +com.google.dagger:hilt-core:2.56 com.google.guava:listenablefuture:1.0 com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.9.0 @@ -110,10 +112,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.1.0 +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -org.jetbrains.kotlin:kotlin-stdlib:2.1.0 +org.jetbrains.kotlin:kotlin-stdlib:2.1.10 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 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 05212a28f..682fbc1b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -45,7 +45,8 @@ android { release { isMinifyEnabled = true applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro") // To publish on the Play store a private signing key is required, but to allow anyone // who clones the code to sign and run the release variant, use the debug signing key. @@ -86,6 +87,7 @@ dependencies { implementation(projects.sync.work) implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive.layout) implementation(libs.androidx.compose.material3.adaptive.navigation) diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 6fca688a1..8457feb9f 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -10,55 +10,55 @@ 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.5.0-beta01 -androidx.collection:collection-ktx:1.5.0-beta01 -androidx.collection:collection:1.5.0-beta01 -androidx.compose.animation:animation-android:1.8.0-alpha07 -androidx.compose.animation:animation-core-android:1.8.0-alpha07 -androidx.compose.animation:animation-core:1.8.0-alpha07 -androidx.compose.animation:animation:1.8.0-alpha07 -androidx.compose.foundation:foundation-android:1.8.0-alpha07 -androidx.compose.foundation:foundation-layout-android:1.8.0-alpha07 -androidx.compose.foundation:foundation-layout:1.8.0-alpha07 -androidx.compose.foundation:foundation:1.8.0-alpha07 -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.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.8.0-alpha07 -androidx.compose.runtime:runtime-saveable-android:1.8.0-alpha07 -androidx.compose.runtime:runtime-saveable:1.8.0-alpha07 -androidx.compose.runtime:runtime-tracing:1.8.0-alpha07 -androidx.compose.runtime:runtime:1.8.0-alpha07 -androidx.compose.ui:ui-android:1.8.0-alpha07 -androidx.compose.ui:ui-geometry-android:1.8.0-alpha07 -androidx.compose.ui:ui-geometry:1.8.0-alpha07 -androidx.compose.ui:ui-graphics-android:1.8.0-alpha07 -androidx.compose.ui:ui-graphics:1.8.0-alpha07 -androidx.compose.ui:ui-text-android:1.8.0-alpha07 -androidx.compose.ui:ui-text:1.8.0-alpha07 -androidx.compose.ui:ui-tooling-preview-android:1.8.0-alpha07 -androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07 -androidx.compose.ui:ui-unit-android:1.8.0-alpha07 -androidx.compose.ui:ui-unit:1.8.0-alpha07 -androidx.compose.ui:ui-util-android:1.8.0-alpha07 -androidx.compose.ui:ui-util:1.8.0-alpha07 -androidx.compose.ui:ui:1.8.0-alpha07 -androidx.compose:compose-bom:2024.12.01 +androidx.collection:collection-jvm:1.5.0-beta03 +androidx.collection:collection-ktx:1.5.0-beta03 +androidx.collection:collection:1.5.0-beta03 +androidx.compose.animation:animation-android:1.8.0-beta02 +androidx.compose.animation:animation-core-android:1.8.0-beta02 +androidx.compose.animation:animation-core:1.8.0-beta02 +androidx.compose.animation:animation:1.8.0-beta02 +androidx.compose.foundation:foundation-android:1.8.0-beta02 +androidx.compose.foundation:foundation-layout-android:1.8.0-beta02 +androidx.compose.foundation:foundation-layout:1.8.0-beta02 +androidx.compose.foundation:foundation:1.8.0-beta02 +androidx.compose.material3.adaptive:adaptive-android:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive-layout-android:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive-layout:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive-navigation-android:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-rc01 +androidx.compose.material3.adaptive:adaptive:1.1.0-rc01 +androidx.compose.material3:material3-adaptive-navigation-suite-android:1.4.0-alpha08 +androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha08 +androidx.compose.material3:material3-android:1.4.0-alpha08 +androidx.compose.material3:material3-window-size-class-android:1.4.0-alpha08 +androidx.compose.material3:material3-window-size-class:1.4.0-alpha08 +androidx.compose.material3:material3:1.4.0-alpha08 +androidx.compose.material:material-icons-core-android:1.7.8 +androidx.compose.material:material-icons-core:1.7.8 +androidx.compose.material:material-icons-extended-android:1.7.8 +androidx.compose.material:material-icons-extended:1.7.8 +androidx.compose.material:material-ripple-android:1.8.0-beta02 +androidx.compose.material:material-ripple:1.8.0-beta02 +androidx.compose.runtime:runtime-android:1.8.0-beta02 +androidx.compose.runtime:runtime-saveable-android:1.8.0-beta02 +androidx.compose.runtime:runtime-saveable:1.8.0-beta02 +androidx.compose.runtime:runtime-tracing:1.8.0-beta02 +androidx.compose.runtime:runtime:1.8.0-beta02 +androidx.compose.ui:ui-android:1.8.0-beta02 +androidx.compose.ui:ui-geometry-android:1.8.0-beta02 +androidx.compose.ui:ui-geometry:1.8.0-beta02 +androidx.compose.ui:ui-graphics-android:1.8.0-beta02 +androidx.compose.ui:ui-graphics:1.8.0-beta02 +androidx.compose.ui:ui-text-android:1.8.0-beta02 +androidx.compose.ui:ui-text:1.8.0-beta02 +androidx.compose.ui:ui-tooling-preview-android:1.8.0-beta02 +androidx.compose.ui:ui-tooling-preview:1.8.0-beta02 +androidx.compose.ui:ui-unit-android:1.8.0-beta02 +androidx.compose.ui:ui-unit:1.8.0-beta02 +androidx.compose.ui:ui-util-android:1.8.0-beta02 +androidx.compose.ui:ui-util:1.8.0-beta02 +androidx.compose.ui:ui:1.8.0-beta02 +androidx.compose:compose-bom-alpha:2025.02.00 androidx.concurrent:concurrent-futures-ktx:1.1.0 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.15.0 @@ -84,6 +84,8 @@ androidx.emoji2:emoji2:1.4.0 androidx.exifinterface:exifinterface:1.3.7 androidx.fragment:fragment:1.5.4 androidx.graphics:graphics-path:1.0.1 +androidx.graphics:graphics-shapes-android:1.0.1 +androidx.graphics:graphics-shapes:1.0.1 androidx.hilt:hilt-common:1.2.0 androidx.hilt:hilt-navigation-compose:1.2.0 androidx.hilt:hilt-navigation:1.2.0 @@ -123,13 +125,17 @@ androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 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 -androidx.room:room-runtime:2.6.1 +androidx.room:room-common-jvm:2.7.2 +androidx.room:room-common:2.7.2 +androidx.room:room-ktx:2.7.2 +androidx.room:room-runtime-android:2.7.2 +androidx.room:room-runtime:2.7.2 androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate:1.2.1 -androidx.sqlite:sqlite-framework:2.4.0 -androidx.sqlite:sqlite:2.4.0 +androidx.sqlite:sqlite-android:2.5.1 +androidx.sqlite:sqlite-framework-android:2.5.1 +androidx.sqlite:sqlite-framework:2.5.1 +androidx.sqlite:sqlite:2.5.1 androidx.startup:startup-runtime:1.1.1 androidx.tracing:tracing-ktx:1.3.0-alpha02 androidx.tracing:tracing-perfetto:1.0.0 @@ -164,10 +170,10 @@ 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.54 -com.google.dagger:dagger:2.54 -com.google.dagger:hilt-android:2.54 -com.google.dagger:hilt-core:2.54 +com.google.dagger:dagger-lint-aar:2.56 +com.google.dagger:dagger:2.56 +com.google.dagger:hilt-android:2.56 +com.google.dagger:hilt-core:2.56 com.google.errorprone:error_prone_annotations:2.26.0 com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-analytics:22.1.2 @@ -212,10 +218,10 @@ 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.1.0 +org.jetbrains.kotlin:kotlin-stdlib-common:2.1.10 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -org.jetbrains.kotlin:kotlin-stdlib:2.1.0 +org.jetbrains.kotlin:kotlin-stdlib:2.1.10 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 diff --git a/app/prodRelease-badging.txt b/app/prodRelease-badging.txt index 0d770604e..ca03c1088 100644 --- a/app/prodRelease-badging.txt +++ b/app/prodRelease-badging.txt @@ -1,5 +1,5 @@ package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15' -sdkVersion:'21' +minSdkVersion:'21' targetSdkVersion:'35' uses-permission: name='android.permission.INTERNET' uses-permission: name='android.permission.ACCESS_NETWORK_STATE' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..24a0b4a16 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,2 @@ +# Repackage classes into the default package to reduce the size of descriptors. +-repackageclasses 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 77f72e5fc..4975e5d65 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 @@ -66,7 +66,7 @@ class NiaApplication : Application(), ImageLoaderFactory { private fun setStrictModePolicy() { if (isDebuggable()) { StrictMode.setThreadPolicy( - Builder().detectAll().penaltyLog().penaltyDeath().build(), + Builder().detectAll().penaltyLog().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 f27b90cbe..b237684ef 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 @@ -21,7 +21,9 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.exclude import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing @@ -166,8 +168,7 @@ internal fun NiaApp( ) }, label = { Text(stringResource(destination.iconTextId)) }, - modifier = - Modifier + modifier = Modifier .testTag("NiaNavItem") .then(if (hasUnread) Modifier.notificationDot() else Modifier), ) @@ -182,7 +183,16 @@ internal fun NiaApp( containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onBackground, contentWindowInsets = WindowInsets(0, 0, 0, 0), - snackbarHost = { SnackbarHost(snackbarHostState) }, + snackbarHost = { + SnackbarHost( + snackbarHostState, + modifier = Modifier.windowInsetsPadding( + WindowInsets.safeDrawing.exclude( + WindowInsets.ime, + ), + ), + ) + }, ) { padding -> Column( Modifier @@ -211,7 +221,7 @@ internal fun NiaApp( actionIconContentDescription = stringResource( id = settingsR.string.feature_settings_top_app_bar_action_icon_description, ), - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + colors = TopAppBarDefaults.topAppBarColors( containerColor = Color.Transparent, ), onActionClick = { onTopAppBarActionClick() }, diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 669c6300a..c0f425c65 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -17,47 +17,53 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane import androidx.activity.compose.BackHandler -import androidx.annotation.Keep +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.LocalMinimumInteractiveComponentSize +import androidx.compose.material3.VerticalDragHandle import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.layout.AnimatedPane -import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective +import androidx.compose.material3.adaptive.layout.defaultDragHandleSemantics +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState +import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior +import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator +import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldPredictiveBackHandler import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute import com.google.samples.apps.nowinandroid.feature.interests.navigation.InterestsRoute import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder +import com.google.samples.apps.nowinandroid.feature.topic.TopicScreen +import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute -import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic -import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable -import java.util.UUID +import kotlin.math.max @Serializable internal object TopicPlaceholderRoute -// TODO: Remove @Keep when https://issuetracker.google.com/353898971 is fixed -@Keep -@Serializable internal object DetailPaneNavHostRoute - fun NavGraphBuilder.interestsListDetailScreen() { composable { InterestsListDetailScreen() @@ -93,70 +99,139 @@ internal fun InterestsListDetailScreen( }, ), ) - BackHandler(listDetailNavigator.canNavigateBack()) { - listDetailNavigator.navigateBack() + val coroutineScope = rememberCoroutineScope() + + val paneExpansionState = rememberPaneExpansionState( + anchors = listOf( + PaneExpansionAnchor.Proportion(0f), + PaneExpansionAnchor.Proportion(0.5f), + PaneExpansionAnchor.Proportion(1f), + ), + ) + + ThreePaneScaffoldPredictiveBackHandler( + listDetailNavigator, + BackNavigationBehavior.PopUntilScaffoldValueChange, + ) + BackHandler( + paneExpansionState.currentAnchor == PaneExpansionAnchor.Proportion(0f) && + listDetailNavigator.isListPaneVisible() && + listDetailNavigator.isDetailPaneVisible(), + ) { + coroutineScope.launch { + paneExpansionState.animateTo(PaneExpansionAnchor.Proportion(1f)) + } } - var nestedNavHostStartRoute by remember { + var topicRoute by remember { val route = selectedTopicId?.let { TopicRoute(id = it) } ?: TopicPlaceholderRoute mutableStateOf(route) } - var nestedNavKey by rememberSaveable( - stateSaver = Saver({ it.toString() }, UUID::fromString), - ) { - mutableStateOf(UUID.randomUUID()) - } - val nestedNavController = key(nestedNavKey) { - rememberNavController() - } fun onTopicClickShowDetailPane(topicId: String) { onTopicClick(topicId) - if (listDetailNavigator.isDetailPaneVisible()) { - // If the detail pane was visible, then use the nestedNavController navigate call - // directly - nestedNavController.navigateToTopic(topicId) { - popUpTo() + topicRoute = TopicRoute(id = topicId) + coroutineScope.launch { + listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + } + if (paneExpansionState.currentAnchor == PaneExpansionAnchor.Proportion(1f)) { + coroutineScope.launch { + paneExpansionState.animateTo(PaneExpansionAnchor.Proportion(0f)) } - } else { - // Otherwise, recreate the NavHost entirely, and start at the new destination - nestedNavHostStartRoute = TopicRoute(id = topicId) - nestedNavKey = UUID.randomUUID() } - listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) } - ListDetailPaneScaffold( - value = listDetailNavigator.scaffoldValue, - directive = listDetailNavigator.scaffoldDirective, + val mutableInteractionSource = remember { MutableInteractionSource() } + val minPaneWidth = 300.dp + + NavigableListDetailPaneScaffold( + navigator = listDetailNavigator, listPane = { AnimatedPane { - InterestsRoute( - onTopicClick = ::onTopicClickShowDetailPane, - highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), - ) + Box( + modifier = Modifier.clipToBounds() + .layout { measurable, constraints -> + val width = max(minPaneWidth.roundToPx(), constraints.maxWidth) + val placeable = measurable.measure( + constraints.copy( + minWidth = minPaneWidth.roundToPx(), + maxWidth = width, + ), + ) + layout(constraints.maxWidth, placeable.height) { + placeable.placeRelative( + x = 0, + y = 0, + ) + } + }, + ) { + InterestsRoute( + onTopicClick = ::onTopicClickShowDetailPane, + shouldHighlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), + ) + } } }, detailPane = { AnimatedPane { - key(nestedNavKey) { - NavHost( - navController = nestedNavController, - startDestination = nestedNavHostStartRoute, - route = DetailPaneNavHostRoute::class, - ) { - topicScreen( - showBackButton = !listDetailNavigator.isListPaneVisible(), - onBackClick = listDetailNavigator::navigateBack, - onTopicClick = ::onTopicClickShowDetailPane, - ) - composable { - TopicDetailPlaceholder() + Box( + modifier = Modifier.clipToBounds() + .layout { measurable, constraints -> + val width = max(minPaneWidth.roundToPx(), constraints.maxWidth) + val placeable = measurable.measure( + constraints.copy( + minWidth = minPaneWidth.roundToPx(), + maxWidth = width, + ), + ) + layout(constraints.maxWidth, placeable.height) { + placeable.placeRelative( + x = constraints.maxWidth - + max(constraints.maxWidth, placeable.width), + y = 0, + ) + } + }, + ) { + AnimatedContent(topicRoute) { route -> + when (route) { + is TopicRoute -> { + TopicScreen( + showBackButton = !listDetailNavigator.isListPaneVisible(), + onBackClick = { + coroutineScope.launch { + listDetailNavigator.navigateBack() + } + }, + onTopicClick = ::onTopicClickShowDetailPane, + viewModel = hiltViewModel( + key = route.id, + ) { factory -> + factory.create(route.id) + }, + ) + } + is TopicPlaceholderRoute -> { + TopicDetailPlaceholder() + } } } } } }, + paneExpansionState = paneExpansionState, + paneExpansionDragHandle = { + VerticalDragHandle( + modifier = Modifier.paneExpansionDraggable( + state = paneExpansionState, + minTouchTargetSize = LocalMinimumInteractiveComponentSize.current, + interactionSource = mutableInteractionSource, + semanticsProperties = paneExpansionState.defaultDragHandleSemantics(), + ), + interactionSource = mutableInteractionSource, + ) + }, ) } diff --git a/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png index 2e6d1037c..382eb032b 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 1acb34aba..cde193ce9 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 d52c844a3..4a8c93d96 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 e6e574347..f0a85e137 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/insets_snackbar_compact_medium.png b/app/src/testDemo/screenshots/insets_snackbar_compact_medium.png index 4e741bdcc..e95e700ba 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 2e76d3a68..db6488e34 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 d39df7b79..f5d5c2d18 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 ec6377143..52a32c385 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 fdbf9ee8b..deeede5e4 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/snackbar_compact_medium.png b/app/src/testDemo/screenshots/snackbar_compact_medium.png index 3f67285a3..910cccfc2 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 893d43d19..8ad664d3a 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/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 8a41dbfcc..5d396d2a4 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 @@ -23,7 +23,6 @@ import org.gradle.api.plugins.JavaPluginExtension import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.configure 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 @@ -78,14 +77,16 @@ internal fun Project.configureKotlinJvm() { 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 + val warningsAsErrors = providers.gradleProperty("warningsAsErrors").map { + it.toBoolean() + }.orElse(false) when (this) { is KotlinAndroidProjectExtension -> compilerOptions is KotlinJvmProjectExtension -> compilerOptions else -> TODO("Unsupported project extension $this ${T::class}") }.apply { jvmTarget = JvmTarget.JVM_11 - allWarningsAsErrors = warningsAsErrors.toBoolean() + allWarningsAsErrors = warningsAsErrors freeCompilerArgs.add( // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt index c7dfd99d0..ac2b5c8af 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt @@ -24,9 +24,9 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.UserData import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import kotlinx.datetime.Clock -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue class UserNewsResourceTest { diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt index a3e373918..1c3ec012a 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt @@ -36,7 +36,7 @@ class TestNewsResourceDao : NewsResourceDao { private val entitiesStateFlow = MutableStateFlow(emptyList()) - internal var topicCrossReferences: List = listOf() + internal var topicCrossReferences: List = emptyList() override fun getNewsResources( useFilterTopicIds: Boolean, diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/model/NewsResourceEntity.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/model/NewsResourceEntity.kt index 9450a24ad..5aed1d1c2 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/model/NewsResourceEntity.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/model/NewsResourceEntity.kt @@ -49,5 +49,5 @@ fun NewsResourceEntity.asExternalModel() = NewsResource( headerImageUrl = headerImageUrl, publishDate = publishDate, type = type, - topics = listOf(), + topics = emptyList(), ) diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt index f85c65677..ece8e0259 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt @@ -45,7 +45,7 @@ fun NiaTopAppBar( actionIcon: ImageVector, actionIconContentDescription: String, modifier: Modifier = Modifier, - colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(), + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), onNavigationClick: () -> Unit = {}, onActionClick: () -> Unit = {}, ) { 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 d217e1116..86fce156f 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 d217e1116..86fce156f 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 d217e1116..86fce156f 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 d217e1116..86fce156f 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 d217e1116..86fce156f 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 d217e1116..86fce156f 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 316d899d2..b88567a42 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 4705b9beb..3bca04850 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 203a2ea2e..a209731e4 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 50a822621..d5e563544 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/Navigation/Navigation_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Navigation/Navigation_dark_androidTheme_notDynamic.png index 5e2a27c65..0940873c6 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 efb1f8756..3575ac09f 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 160a72d61..44bb3b5c6 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 b4b4842c3..b2dff990a 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 c3200091b..9409b984d 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 877c45cd0..233f86b35 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 9fd6fc843..590c878a0 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/Tag/Tag_dark_androidTheme_notDynamic.png b/core/designsystem/src/test/screenshots/Tag/Tag_dark_androidTheme_notDynamic.png index 2a065eb57..4897d2a70 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 c532a307e..2e5bcbbbd 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 61d3321b8..dcb18ab95 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 7cfdbb2a7..59a1aabd0 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 fc29b6e11..c9f6523f3 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 ed825ba71..fb1023ece 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 8acc6b0a8..224f34303 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 3617feab1..727910474 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 418e0f88d..2c09fedc3 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 921698d22..cd52d5494 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 497561ef7..9e275f470 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 4988252e5..64034e77e 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 6ab40ebf9..24116fcd2 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 d9d014a18..3f01e8743 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/network/build.gradle.kts b/core/network/build.gradle.kts index d12482a56..bf4dd9153 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -14,12 +14,15 @@ * limitations under the License. */ +import com.android.build.api.variant.BuildConfigField +import java.io.StringReader +import java.util.Properties + plugins { alias(libs.plugins.nowinandroid.android.library) alias(libs.plugins.nowinandroid.android.library.jacoco) alias(libs.plugins.nowinandroid.hilt) id("kotlinx-serialization") - id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") } android { @@ -34,10 +37,6 @@ android { } } -secrets { - defaultPropertiesFileName = "secrets.defaults.properties" -} - dependencies { api(libs.kotlinx.datetime) api(projects.core.common) @@ -52,3 +51,22 @@ dependencies { testImplementation(libs.kotlinx.coroutines.test) } + +val backendUrl = providers.fileContents( + isolated.rootProject.projectDirectory.file("local.properties") +).asText.map { text -> + val properties = Properties() + properties.load(StringReader(text)) + if (properties.containsKey("BACKEND_URL")) + (properties["BACKEND_URL"] as String) + else "http://example.com" + // Move to returning `properties["BACKEND_URL"] as String?` after upgrading to Gradle 9.0.0 +}.orElse("http://example.com") + +androidComponents { + onVariants { + it.buildConfigFields.put("BACKEND_URL", backendUrl.map { value -> + BuildConfigField(type = "String", value = """"$value"""", comment = null) + }) + } +} diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/model/NetworkNewsResource.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/model/NetworkNewsResource.kt index 7b66af796..92e8e9ffa 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/model/NetworkNewsResource.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/model/NetworkNewsResource.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.core.network.model +import android.annotation.SuppressLint import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import kotlinx.datetime.Instant import kotlinx.serialization.Serializable @@ -23,6 +24,7 @@ import kotlinx.serialization.Serializable /** * Network representation of [NewsResource] when fetched from /newsresources */ +@SuppressLint("UnsafeOptInUsageError") @Serializable data class NetworkNewsResource( val id: String, @@ -32,5 +34,5 @@ data class NetworkNewsResource( val headerImageUrl: String, val publishDate: Instant, val type: String, - val topics: List = listOf(), + val topics: List = emptyList(), ) diff --git a/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenLoading_foldable.png index a699345c2..538c9032c 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 30c8fdad7..afd7dd708 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 f54966dbc..1c197e8f0 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/ForYouScreenPopulatedFeed_foldable.png b/feature/foryou/src/test/screenshots/ForYouScreenPopulatedFeed_foldable.png index 0da7e19ac..bc9c7dbec 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 a7b775315..1498f9b7b 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 e7f0cc050..2b16ff76f 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 f5d68fce2..7ea92f8a8 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 b04c632c3..b2faa3a28 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 6c4d66b28..4c30995b2 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 f7c20a890..05f1ba1ed 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/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt index 468550878..9b18ac89b 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt @@ -38,7 +38,7 @@ import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent fun InterestsRoute( onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, - highlightSelectedTopic: Boolean = false, + shouldHighlightSelectedTopic: Boolean = false, viewModel: InterestsViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -50,7 +50,7 @@ fun InterestsRoute( viewModel.onTopicClick(it) onTopicClick(it) }, - highlightSelectedTopic = highlightSelectedTopic, + shouldHighlightSelectedTopic = shouldHighlightSelectedTopic, modifier = modifier, ) } @@ -61,7 +61,7 @@ internal fun InterestsScreen( followTopic: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, - highlightSelectedTopic: Boolean = false, + shouldHighlightSelectedTopic: Boolean = false, ) { Column( modifier = modifier, @@ -70,7 +70,6 @@ internal fun InterestsScreen( when (uiState) { InterestsUiState.Loading -> NiaLoadingWheel( - modifier = modifier, contentDesc = stringResource(id = R.string.feature_interests_loading), ) @@ -80,8 +79,7 @@ internal fun InterestsScreen( onTopicClick = onTopicClick, onFollowButtonClick = followTopic, selectedTopicId = uiState.selectedTopicId, - highlightSelectedTopic = highlightSelectedTopic, - modifier = modifier, + shouldHighlightSelectedTopic = shouldHighlightSelectedTopic, ) is InterestsUiState.Empty -> InterestsEmptyScreen() diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt index 83058c12e..133c2bedd 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt @@ -49,7 +49,7 @@ fun TopicsTabContent( modifier: Modifier = Modifier, withBottomSpacer: Boolean = true, selectedTopicId: String? = null, - highlightSelectedTopic: Boolean = false, + shouldHighlightSelectedTopic: Boolean = false, ) { Box( modifier = modifier @@ -66,7 +66,7 @@ fun TopicsTabContent( topics.forEach { followableTopic -> val topicId = followableTopic.topic.id item(key = topicId) { - val isSelected = highlightSelectedTopic && topicId == selectedTopicId + val isSelected = shouldHighlightSelectedTopic && topicId == selectedTopicId InterestsItem( name = followableTopic.topic.name, following = followableTopic.isFollowed, @@ -75,6 +75,7 @@ fun TopicsTabContent( onClick = { onTopicClick(topicId) }, onFollowButtonClick = { onFollowButtonClick(topicId, it) }, isSelected = isSelected, + modifier = Modifier.fillMaxWidth(), ) } } diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index ba8baad14..8865da463 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -16,10 +16,8 @@ package com.google.samples.apps.nowinandroid.feature.topic -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.toRoute import com.google.samples.apps.nowinandroid.core.data.repository.NewsResourceQuery import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository @@ -29,7 +27,9 @@ import com.google.samples.apps.nowinandroid.core.model.data.Topic import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.result.Result import com.google.samples.apps.nowinandroid.core.result.asResult -import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -38,18 +38,14 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import javax.inject.Inject -@HiltViewModel -class TopicViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, +@HiltViewModel(assistedFactory = TopicViewModel.Factory::class) +class TopicViewModel @AssistedInject constructor( private val userDataRepository: UserDataRepository, topicsRepository: TopicsRepository, userNewsResourceRepository: UserNewsResourceRepository, + @Assisted val topicId: String, ) : ViewModel() { - - val topicId = savedStateHandle.toRoute().id - val topicUiState: StateFlow = topicUiState( topicId = topicId, userDataRepository = userDataRepository, @@ -89,6 +85,13 @@ class TopicViewModel @Inject constructor( userDataRepository.setNewsResourceViewed(newsResourceId, viewed) } } + + @AssistedFactory + interface Factory { + fun create( + topicId: String, + ): TopicViewModel + } } private fun topicUiState( diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt index fabb82b10..69059c81d 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt @@ -16,11 +16,14 @@ package com.google.samples.apps.nowinandroid.feature.topic.navigation +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptionsBuilder import androidx.navigation.compose.composable +import androidx.navigation.toRoute import com.google.samples.apps.nowinandroid.feature.topic.TopicScreen +import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel import kotlinx.serialization.Serializable @Serializable data class TopicRoute(val id: String) @@ -36,11 +39,17 @@ fun NavGraphBuilder.topicScreen( onBackClick: () -> Unit, onTopicClick: (String) -> Unit, ) { - composable { + composable { entry -> + val id = entry.toRoute().id TopicScreen( showBackButton = showBackButton, onBackClick = onBackClick, onTopicClick = onTopicClick, + viewModel = hiltViewModel( + key = id, + ) { factory -> + factory.create(id) + }, ) } } diff --git a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index 34f21a59a..58242110d 100644 --- a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -16,8 +16,6 @@ package com.google.samples.apps.nowinandroid.feature.topic -import androidx.lifecycle.SavedStateHandle -import androidx.navigation.testing.invoke import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.NewsResource @@ -26,7 +24,6 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepo import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule -import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @@ -37,22 +34,13 @@ import kotlinx.datetime.Instant import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import kotlin.test.assertEquals import kotlin.test.assertIs /** * To learn more about how this test handles Flows created with stateIn, see * https://developer.android.com/kotlin/flow/test#statein - * - * These tests use Robolectric because the subject under test (the ViewModel) uses - * `SavedStateHandle.toRoute` which has a dependency on `android.os.Bundle`. - * - * TODO: Remove Robolectric if/when AndroidX Navigation API is updated to remove Android dependency. - * * See b/340966212. */ -@RunWith(RobolectricTestRunner::class) class TopicViewModelTest { @get:Rule @@ -70,12 +58,10 @@ class TopicViewModelTest { @Before fun setup() { viewModel = TopicViewModel( - savedStateHandle = SavedStateHandle( - route = TopicRoute(id = testInputTopics[0].topic.id), - ), userDataRepository = userDataRepository, topicsRepository = topicsRepository, userNewsResourceRepository = userNewsResourceRepository, + topicId = testInputTopics[0].topic.id, ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9357809b0..2b6f96968 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,13 +2,14 @@ accompanist = "0.37.0" androidDesugarJdkLibs = "2.1.4" # AGP and tools should be updated together -androidGradlePlugin = "8.7.3" -androidTools = "31.7.3" +androidGradlePlugin = "8.9.0" +androidTools = "31.9.0" androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" -androidxComposeBom = "2024.12.01" +androidxComposeBom = "2025.02.00" androidxComposeFoundation = "1.8.0-alpha07" +androidxComposeMaterial3Adaptive = "1.1.0-rc01" androidxComposeRuntimeTracing = "1.7.6" androidxCore = "1.15.0" androidxCoreSplashscreen = "1.0.1" @@ -17,14 +18,14 @@ androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.8.7" androidxLintGradle = "1.0.0-alpha03" -androidxMacroBenchmark = "1.3.3" +androidxMacroBenchmark = "1.3.4" androidxMetrics = "1.0.0-beta01" androidxNavigation = "2.8.5" androidxProfileinstaller = "1.4.1" -androidxTestCore = "1.6.1" -androidxTestExt = "1.2.1" -androidxTestRules = "1.6.1" -androidxTestRunner = "1.6.2" +androidxTestCore = "1.7.0-rc01" +androidxTestExt = "1.3.0-rc01" +androidxTestRules = "1.7.0-rc01" +androidxTestRunner = "1.7.0-rc01" androidxTracing = "1.3.0-alpha02" androidxUiAutomator = "2.3.0" androidxWindowManager = "1.3.0" @@ -37,15 +38,15 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.2" googleOss = "17.1.0" googleOssPlugin = "0.10.6" -hilt = "2.54" +hilt = "2.56" hiltExt = "1.2.0" jacoco = "0.8.12" junit4 = "4.13.2" -kotlin = "2.1.0" +kotlin = "2.1.10" kotlinxCoroutines = "1.10.1" kotlinxDatetime = "0.6.1" kotlinxSerializationJson = "1.8.0" -ksp = "2.1.0-1.0.29" +ksp = "2.1.10-1.0.31" moduleGraph = "2.7.1" okhttp = "4.12.0" protobuf = "4.29.2" @@ -54,7 +55,7 @@ retrofit = "2.11.0" retrofitKotlinxSerializationJson = "1.0.0" robolectric = "4.14.1" roborazzi = "1.39.0" -room = "2.6.1" +room = "2.7.2" secrets = "2.0.1" truth = "1.4.4" turbine = "1.2.0" @@ -69,15 +70,15 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidxBrowser" } -androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom-alpha", version.ref = "androidxComposeBom" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxComposeFoundation" } androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3-navigationSuite = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" } -androidx-compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive" } -androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout" } -androidx-compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation" } +androidx-compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "androidxComposeMaterial3Adaptive" } +androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout", version.ref = "androidxComposeMaterial3Adaptive" } +androidx-compose-material3-adaptive-navigation = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation", version.ref = "androidxComposeMaterial3Adaptive" } androidx-compose-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793a..c99f97449 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6..f3b75f3b0 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/secrets.defaults.properties b/secrets.defaults.properties deleted file mode 100644 index 3b5457bd9..000000000 --- a/secrets.defaults.properties +++ /dev/null @@ -1,4 +0,0 @@ -## This file provides default values to modules using the secrets-gradle-plugin. It is necessary -# because the secrets properties file is not under source control so CI builds will fail without -# default values. -BACKEND_URL="http://example.com" \ No newline at end of file