Merge branch 'main' into fix/coverage-report

Change-Id: Icf249596a5789c1305673cd962744a04d31108c5
pull/1812/head
blue928sky 2 months ago
commit 3cf03392c9

@ -1,6 +1,7 @@
name: Build name: Build
on: on:
workflow_dispatch:
push: push:
branches: branches:
- main - main
@ -29,16 +30,19 @@ jobs:
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/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 uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: 17 java-version: 21
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 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 - name: Check build-logic
run: ./gradlew :build-logic:convention:check run: ./gradlew :build-logic:convention:check
@ -139,11 +143,26 @@ jobs:
name: lint-reports name: lint-reports
path: '**/build/reports/lint-results-*.html' path: '**/build/reports/lint-results-*.html'
- name: Upload lint reports (SARIF) - name: Upload lint reports (SARIF) for app module
if: ${{ !cancelled() && hashFiles('**/*.sarif') != '' }} if: ${{ !cancelled() && hashFiles('app/**/*.sarif') != '' }}
uses: github/codeql-action/upload-sarif@v3 uses: github/codeql-action/upload-sarif@v3
with: 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 - name: Check badging
run: ./gradlew :app:checkProdReleaseBadging run: ./gradlew :app:checkProdReleaseBadging
@ -180,16 +199,19 @@ jobs:
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/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 uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: 17 java-version: 21
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 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 - name: Build projects and run instrumentation tests
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2

@ -1,6 +1,7 @@
name: NightlyBaselineProfiles name: NightlyBaselineProfiles
on: on:
workflow_dispatch:
schedule: schedule:
- cron: '42 4 * * *' - cron: '42 4 * * *'
@ -39,6 +40,9 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 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 - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3

@ -1,6 +1,7 @@
name: GitHub Release with APKs name: GitHub Release with APKs
on: on:
workflow_dispatch:
push: push:
tags: tags:
- 'v*' - 'v*'
@ -36,6 +37,9 @@ jobs:
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4
with: with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 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 - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3

@ -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.

@ -0,0 +1 @@
* @dturner

@ -9,48 +9,48 @@ androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0 androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0 androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.8.0 androidx.browser:browser:1.8.0
androidx.collection:collection-jvm:1.5.0-beta01 androidx.collection:collection-jvm:1.5.0-beta03
androidx.collection:collection-ktx:1.5.0-beta01 androidx.collection:collection-ktx:1.5.0-beta03
androidx.collection:collection:1.5.0-beta01 androidx.collection:collection:1.5.0-beta03
androidx.compose.animation:animation-android:1.8.0-alpha07 androidx.compose.animation:animation-android:1.8.0-beta02
androidx.compose.animation:animation-core-android:1.8.0-alpha07 androidx.compose.animation:animation-core-android:1.8.0-beta02
androidx.compose.animation:animation-core:1.8.0-alpha07 androidx.compose.animation:animation-core:1.8.0-beta02
androidx.compose.animation:animation:1.8.0-alpha07 androidx.compose.animation:animation:1.8.0-beta02
androidx.compose.foundation:foundation-android:1.8.0-alpha07 androidx.compose.foundation:foundation-android:1.8.0-beta02
androidx.compose.foundation:foundation-layout-android:1.8.0-alpha07 androidx.compose.foundation:foundation-layout-android:1.8.0-beta02
androidx.compose.foundation:foundation-layout:1.8.0-alpha07 androidx.compose.foundation:foundation-layout:1.8.0-beta02
androidx.compose.foundation:foundation:1.8.0-alpha07 androidx.compose.foundation:foundation:1.8.0-beta02
androidx.compose.material3.adaptive:adaptive-android:1.0.0 androidx.compose.material3.adaptive:adaptive-android:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive:1.0.0 androidx.compose.material3.adaptive:adaptive:1.1.0-rc01
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.1 androidx.compose.material3:material3-adaptive-navigation-suite-android:1.4.0-alpha08
androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1 androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha08
androidx.compose.material3:material3-android:1.3.1 androidx.compose.material3:material3-android:1.4.0-alpha08
androidx.compose.material3:material3:1.3.1 androidx.compose.material3:material3:1.4.0-alpha08
androidx.compose.material:material-icons-core-android:1.7.6 androidx.compose.material:material-icons-core-android:1.7.8
androidx.compose.material:material-icons-core:1.7.6 androidx.compose.material:material-icons-core:1.7.8
androidx.compose.material:material-icons-extended-android:1.7.6 androidx.compose.material:material-icons-extended-android:1.7.8
androidx.compose.material:material-icons-extended:1.7.6 androidx.compose.material:material-icons-extended:1.7.8
androidx.compose.material:material-ripple-android:1.7.6 androidx.compose.material:material-ripple-android:1.8.0-beta02
androidx.compose.material:material-ripple:1.7.6 androidx.compose.material:material-ripple:1.8.0-beta02
androidx.compose.runtime:runtime-android:1.8.0-alpha07 androidx.compose.runtime:runtime-android:1.8.0-beta02
androidx.compose.runtime:runtime-saveable-android:1.8.0-alpha07 androidx.compose.runtime:runtime-saveable-android:1.8.0-beta02
androidx.compose.runtime:runtime-saveable:1.8.0-alpha07 androidx.compose.runtime:runtime-saveable:1.8.0-beta02
androidx.compose.runtime:runtime:1.8.0-alpha07 androidx.compose.runtime:runtime:1.8.0-beta02
androidx.compose.ui:ui-android:1.8.0-alpha07 androidx.compose.ui:ui-android:1.8.0-beta02
androidx.compose.ui:ui-geometry-android:1.8.0-alpha07 androidx.compose.ui:ui-geometry-android:1.8.0-beta02
androidx.compose.ui:ui-geometry:1.8.0-alpha07 androidx.compose.ui:ui-geometry:1.8.0-beta02
androidx.compose.ui:ui-graphics-android:1.8.0-alpha07 androidx.compose.ui:ui-graphics-android:1.8.0-beta02
androidx.compose.ui:ui-graphics:1.8.0-alpha07 androidx.compose.ui:ui-graphics:1.8.0-beta02
androidx.compose.ui:ui-text-android:1.8.0-alpha07 androidx.compose.ui:ui-text-android:1.8.0-beta02
androidx.compose.ui:ui-text:1.8.0-alpha07 androidx.compose.ui:ui-text:1.8.0-beta02
androidx.compose.ui:ui-tooling-preview-android:1.8.0-alpha07 androidx.compose.ui:ui-tooling-preview-android:1.8.0-beta02
androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07 androidx.compose.ui:ui-tooling-preview:1.8.0-beta02
androidx.compose.ui:ui-unit-android:1.8.0-alpha07 androidx.compose.ui:ui-unit-android:1.8.0-beta02
androidx.compose.ui:ui-unit:1.8.0-alpha07 androidx.compose.ui:ui-unit:1.8.0-beta02
androidx.compose.ui:ui-util-android:1.8.0-alpha07 androidx.compose.ui:ui-util-android:1.8.0-beta02
androidx.compose.ui:ui-util:1.8.0-alpha07 androidx.compose.ui:ui-util:1.8.0-beta02
androidx.compose.ui:ui:1.8.0-alpha07 androidx.compose.ui:ui:1.8.0-beta02
androidx.compose:compose-bom:2024.12.01 androidx.compose:compose-bom-alpha:2025.02.00
androidx.concurrent:concurrent-futures:1.1.0 androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.13.1 androidx.core:core-ktx:1.13.1
androidx.core:core: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.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.1 androidx.fragment:fragment:1.5.1
androidx.graphics:graphics-path:1.0.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.interpolator:interpolator:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.8.7 androidx.lifecycle:lifecycle-common-java8:2.8.7
androidx.lifecycle:lifecycle-common-jvm: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 androidx.window:window:1.3.0
com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.accompanist:accompanist-drawablepainter:0.32.0
com.google.code.findbugs:jsr305:3.0.2 com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger-lint-aar:2.56
com.google.dagger:dagger:2.54 com.google.dagger:dagger:2.56
com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-android:2.56
com.google.dagger:hilt-core:2.54 com.google.dagger:hilt-core:2.56
com.google.guava:listenablefuture:1.0 com.google.guava:listenablefuture:1.0
com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.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 io.coil-kt:coil:2.7.0
jakarta.inject:jakarta.inject-api:2.0.1 jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject: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-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8: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-android:1.8.1
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1

@ -45,7 +45,8 @@ android {
release { release {
isMinifyEnabled = true isMinifyEnabled = true
applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix 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 // 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. // 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(projects.sync.work)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout) implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation) implementation(libs.androidx.compose.material3.adaptive.navigation)

@ -10,55 +10,55 @@ androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0 androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0 androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.8.0 androidx.browser:browser:1.8.0
androidx.collection:collection-jvm:1.5.0-beta01 androidx.collection:collection-jvm:1.5.0-beta03
androidx.collection:collection-ktx:1.5.0-beta01 androidx.collection:collection-ktx:1.5.0-beta03
androidx.collection:collection:1.5.0-beta01 androidx.collection:collection:1.5.0-beta03
androidx.compose.animation:animation-android:1.8.0-alpha07 androidx.compose.animation:animation-android:1.8.0-beta02
androidx.compose.animation:animation-core-android:1.8.0-alpha07 androidx.compose.animation:animation-core-android:1.8.0-beta02
androidx.compose.animation:animation-core:1.8.0-alpha07 androidx.compose.animation:animation-core:1.8.0-beta02
androidx.compose.animation:animation:1.8.0-alpha07 androidx.compose.animation:animation:1.8.0-beta02
androidx.compose.foundation:foundation-android:1.8.0-alpha07 androidx.compose.foundation:foundation-android:1.8.0-beta02
androidx.compose.foundation:foundation-layout-android:1.8.0-alpha07 androidx.compose.foundation:foundation-layout-android:1.8.0-beta02
androidx.compose.foundation:foundation-layout:1.8.0-alpha07 androidx.compose.foundation:foundation-layout:1.8.0-beta02
androidx.compose.foundation:foundation:1.8.0-alpha07 androidx.compose.foundation:foundation:1.8.0-beta02
androidx.compose.material3.adaptive:adaptive-android:1.0.0 androidx.compose.material3.adaptive:adaptive-android:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0 androidx.compose.material3.adaptive:adaptive-layout-android:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive-layout:1.0.0 androidx.compose.material3.adaptive:adaptive-layout:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation-android:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive-navigation:1.0.0 androidx.compose.material3.adaptive:adaptive-navigation:1.1.0-rc01
androidx.compose.material3.adaptive:adaptive:1.0.0 androidx.compose.material3.adaptive:adaptive:1.1.0-rc01
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.1 androidx.compose.material3:material3-adaptive-navigation-suite-android:1.4.0-alpha08
androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1 androidx.compose.material3:material3-adaptive-navigation-suite:1.4.0-alpha08
androidx.compose.material3:material3-android:1.3.1 androidx.compose.material3:material3-android:1.4.0-alpha08
androidx.compose.material3:material3-window-size-class-android:1.3.1 androidx.compose.material3:material3-window-size-class-android:1.4.0-alpha08
androidx.compose.material3:material3-window-size-class:1.3.1 androidx.compose.material3:material3-window-size-class:1.4.0-alpha08
androidx.compose.material3:material3:1.3.1 androidx.compose.material3:material3:1.4.0-alpha08
androidx.compose.material:material-icons-core-android:1.7.6 androidx.compose.material:material-icons-core-android:1.7.8
androidx.compose.material:material-icons-core:1.7.6 androidx.compose.material:material-icons-core:1.7.8
androidx.compose.material:material-icons-extended-android:1.7.6 androidx.compose.material:material-icons-extended-android:1.7.8
androidx.compose.material:material-icons-extended:1.7.6 androidx.compose.material:material-icons-extended:1.7.8
androidx.compose.material:material-ripple-android:1.7.6 androidx.compose.material:material-ripple-android:1.8.0-beta02
androidx.compose.material:material-ripple:1.7.6 androidx.compose.material:material-ripple:1.8.0-beta02
androidx.compose.runtime:runtime-android:1.8.0-alpha07 androidx.compose.runtime:runtime-android:1.8.0-beta02
androidx.compose.runtime:runtime-saveable-android:1.8.0-alpha07 androidx.compose.runtime:runtime-saveable-android:1.8.0-beta02
androidx.compose.runtime:runtime-saveable:1.8.0-alpha07 androidx.compose.runtime:runtime-saveable:1.8.0-beta02
androidx.compose.runtime:runtime-tracing:1.8.0-alpha07 androidx.compose.runtime:runtime-tracing:1.8.0-beta02
androidx.compose.runtime:runtime:1.8.0-alpha07 androidx.compose.runtime:runtime:1.8.0-beta02
androidx.compose.ui:ui-android:1.8.0-alpha07 androidx.compose.ui:ui-android:1.8.0-beta02
androidx.compose.ui:ui-geometry-android:1.8.0-alpha07 androidx.compose.ui:ui-geometry-android:1.8.0-beta02
androidx.compose.ui:ui-geometry:1.8.0-alpha07 androidx.compose.ui:ui-geometry:1.8.0-beta02
androidx.compose.ui:ui-graphics-android:1.8.0-alpha07 androidx.compose.ui:ui-graphics-android:1.8.0-beta02
androidx.compose.ui:ui-graphics:1.8.0-alpha07 androidx.compose.ui:ui-graphics:1.8.0-beta02
androidx.compose.ui:ui-text-android:1.8.0-alpha07 androidx.compose.ui:ui-text-android:1.8.0-beta02
androidx.compose.ui:ui-text:1.8.0-alpha07 androidx.compose.ui:ui-text:1.8.0-beta02
androidx.compose.ui:ui-tooling-preview-android:1.8.0-alpha07 androidx.compose.ui:ui-tooling-preview-android:1.8.0-beta02
androidx.compose.ui:ui-tooling-preview:1.8.0-alpha07 androidx.compose.ui:ui-tooling-preview:1.8.0-beta02
androidx.compose.ui:ui-unit-android:1.8.0-alpha07 androidx.compose.ui:ui-unit-android:1.8.0-beta02
androidx.compose.ui:ui-unit:1.8.0-alpha07 androidx.compose.ui:ui-unit:1.8.0-beta02
androidx.compose.ui:ui-util-android:1.8.0-alpha07 androidx.compose.ui:ui-util-android:1.8.0-beta02
androidx.compose.ui:ui-util:1.8.0-alpha07 androidx.compose.ui:ui-util:1.8.0-beta02
androidx.compose.ui:ui:1.8.0-alpha07 androidx.compose.ui:ui:1.8.0-beta02
androidx.compose:compose-bom:2024.12.01 androidx.compose:compose-bom-alpha:2025.02.00
androidx.concurrent:concurrent-futures-ktx:1.1.0 androidx.concurrent:concurrent-futures-ktx:1.1.0
androidx.concurrent:concurrent-futures:1.1.0 androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.15.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.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.4 androidx.fragment:fragment:1.5.4
androidx.graphics:graphics-path:1.0.1 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-common:1.2.0
androidx.hilt:hilt-navigation-compose:1.2.0 androidx.hilt:hilt-navigation-compose:1.2.0
androidx.hilt:hilt-navigation: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.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.4.1 androidx.profileinstaller:profileinstaller:1.4.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1 androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.room:room-common:2.6.1 androidx.room:room-common-jvm:2.7.2
androidx.room:room-ktx:2.6.1 androidx.room:room-common:2.7.2
androidx.room:room-runtime:2.6.1 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-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1 androidx.savedstate:savedstate:1.2.1
androidx.sqlite:sqlite-framework:2.4.0 androidx.sqlite:sqlite-android:2.5.1
androidx.sqlite:sqlite:2.4.0 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.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.3.0-alpha02 androidx.tracing:tracing-ktx:1.3.0-alpha02
androidx.tracing:tracing-perfetto:1.0.0 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-stats:17.0.2
com.google.android.gms:play-services-tasks:18.2.0 com.google.android.gms:play-services-tasks:18.2.0
com.google.code.findbugs:jsr305:3.0.2 com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.54 com.google.dagger:dagger-lint-aar:2.56
com.google.dagger:dagger:2.54 com.google.dagger:dagger:2.56
com.google.dagger:hilt-android:2.54 com.google.dagger:hilt-android:2.56
com.google.dagger:hilt-core:2.54 com.google.dagger:hilt-core:2.56
com.google.errorprone:error_prone_annotations:2.26.0 com.google.errorprone:error_prone_annotations:2.26.0
com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics:22.1.2 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.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22 org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.9.22
org.jetbrains.kotlin:kotlin-parcelize-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-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8: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-android:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-bom: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-jvm:1.10.1

@ -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' 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' targetSdkVersion:'35'
uses-permission: name='android.permission.INTERNET' uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.ACCESS_NETWORK_STATE'

@ -0,0 +1,2 @@
# Repackage classes into the default package to reduce the size of descriptors.
-repackageclasses

@ -66,7 +66,7 @@ class NiaApplication : Application(), ImageLoaderFactory {
private fun setStrictModePolicy() { private fun setStrictModePolicy() {
if (isDebuggable()) { if (isDebuggable()) {
StrictMode.setThreadPolicy( StrictMode.setThreadPolicy(
Builder().detectAll().penaltyLog().penaltyDeath().build(), Builder().detectAll().penaltyLog().build(),
) )
} }
} }

@ -21,7 +21,9 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
@ -166,8 +168,7 @@ internal fun NiaApp(
) )
}, },
label = { Text(stringResource(destination.iconTextId)) }, label = { Text(stringResource(destination.iconTextId)) },
modifier = modifier = Modifier
Modifier
.testTag("NiaNavItem") .testTag("NiaNavItem")
.then(if (hasUnread) Modifier.notificationDot() else Modifier), .then(if (hasUnread) Modifier.notificationDot() else Modifier),
) )
@ -182,7 +183,16 @@ internal fun NiaApp(
containerColor = Color.Transparent, containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
contentWindowInsets = WindowInsets(0, 0, 0, 0), contentWindowInsets = WindowInsets(0, 0, 0, 0),
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = {
SnackbarHost(
snackbarHostState,
modifier = Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.exclude(
WindowInsets.ime,
),
),
)
},
) { padding -> ) { padding ->
Column( Column(
Modifier Modifier
@ -211,7 +221,7 @@ internal fun NiaApp(
actionIconContentDescription = stringResource( actionIconContentDescription = stringResource(
id = settingsR.string.feature_settings_top_app_bar_action_icon_description, id = settingsR.string.feature_settings_top_app_bar_action_icon_description,
), ),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent, containerColor = Color.Transparent,
), ),
onActionClick = { onTopAppBarActionClick() }, onActionClick = { onTopAppBarActionClick() },

@ -17,47 +17,53 @@
package com.google.samples.apps.nowinandroid.ui.interests2pane package com.google.samples.apps.nowinandroid.ui.interests2pane
import androidx.activity.compose.BackHandler 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.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.AnimatedPane 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.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue 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.ThreePaneScaffoldDestinationItem
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective 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.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldPredictiveBackHandler
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable 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.InterestsRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.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.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.TopicRoute
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic import kotlinx.coroutines.launch
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.util.UUID import kotlin.math.max
@Serializable internal object TopicPlaceholderRoute @Serializable internal object TopicPlaceholderRoute
// TODO: Remove @Keep when https://issuetracker.google.com/353898971 is fixed
@Keep
@Serializable internal object DetailPaneNavHostRoute
fun NavGraphBuilder.interestsListDetailScreen() { fun NavGraphBuilder.interestsListDetailScreen() {
composable<InterestsRoute> { composable<InterestsRoute> {
InterestsListDetailScreen() InterestsListDetailScreen()
@ -93,69 +99,138 @@ internal fun InterestsListDetailScreen(
}, },
), ),
) )
BackHandler(listDetailNavigator.canNavigateBack()) { val coroutineScope = rememberCoroutineScope()
listDetailNavigator.navigateBack()
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 val route = selectedTopicId?.let { TopicRoute(id = it) } ?: TopicPlaceholderRoute
mutableStateOf(route) mutableStateOf(route)
} }
var nestedNavKey by rememberSaveable(
stateSaver = Saver({ it.toString() }, UUID::fromString),
) {
mutableStateOf(UUID.randomUUID())
}
val nestedNavController = key(nestedNavKey) {
rememberNavController()
}
fun onTopicClickShowDetailPane(topicId: String) { fun onTopicClickShowDetailPane(topicId: String) {
onTopicClick(topicId) onTopicClick(topicId)
if (listDetailNavigator.isDetailPaneVisible()) { topicRoute = TopicRoute(id = topicId)
// If the detail pane was visible, then use the nestedNavController navigate call coroutineScope.launch {
// directly listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
nestedNavController.navigateToTopic(topicId) { }
popUpTo<DetailPaneNavHostRoute>() 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( val mutableInteractionSource = remember { MutableInteractionSource() }
value = listDetailNavigator.scaffoldValue, val minPaneWidth = 300.dp
directive = listDetailNavigator.scaffoldDirective,
NavigableListDetailPaneScaffold(
navigator = listDetailNavigator,
listPane = { listPane = {
AnimatedPane { AnimatedPane {
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( InterestsRoute(
onTopicClick = ::onTopicClickShowDetailPane, onTopicClick = ::onTopicClickShowDetailPane,
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), shouldHighlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
) )
} }
}
}, },
detailPane = { detailPane = {
AnimatedPane { AnimatedPane {
key(nestedNavKey) { Box(
NavHost( modifier = Modifier.clipToBounds()
navController = nestedNavController, .layout { measurable, constraints ->
startDestination = nestedNavHostStartRoute, val width = max(minPaneWidth.roundToPx(), constraints.maxWidth)
route = DetailPaneNavHostRoute::class, 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,
)
}
},
) { ) {
topicScreen( AnimatedContent(topicRoute) { route ->
when (route) {
is TopicRoute -> {
TopicScreen(
showBackButton = !listDetailNavigator.isListPaneVisible(), showBackButton = !listDetailNavigator.isListPaneVisible(),
onBackClick = listDetailNavigator::navigateBack, onBackClick = {
coroutineScope.launch {
listDetailNavigator.navigateBack()
}
},
onTopicClick = ::onTopicClickShowDetailPane, onTopicClick = ::onTopicClickShowDetailPane,
viewModel = hiltViewModel<TopicViewModel, TopicViewModel.Factory>(
key = route.id,
) { factory ->
factory.create(route.id)
},
) )
composable<TopicPlaceholderRoute> { }
is TopicPlaceholderRoute -> {
TopicDetailPlaceholder() TopicDetailPlaceholder()
} }
} }
} }
} }
}
},
paneExpansionState = paneExpansionState,
paneExpansionDragHandle = {
VerticalDragHandle(
modifier = Modifier.paneExpansionDraggable(
state = paneExpansionState,
minTouchTargetSize = LocalMinimumInteractiveComponentSize.current,
interactionSource = mutableInteractionSource,
semanticsProperties = paneExpansionState.defaultDragHandleSemantics(),
),
interactionSource = mutableInteractionSource,
)
}, },
) )
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

@ -23,7 +23,6 @@ import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies 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.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
@ -78,14 +77,16 @@ internal fun Project.configureKotlinJvm() {
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> { private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
// Treat all Kotlin warnings as errors (disabled by default) // Treat all Kotlin warnings as errors (disabled by default)
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties // 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) { when (this) {
is KotlinAndroidProjectExtension -> compilerOptions is KotlinAndroidProjectExtension -> compilerOptions
is KotlinJvmProjectExtension -> compilerOptions is KotlinJvmProjectExtension -> compilerOptions
else -> TODO("Unsupported project extension $this ${T::class}") else -> TODO("Unsupported project extension $this ${T::class}")
}.apply { }.apply {
jvmTarget = JvmTarget.JVM_11 jvmTarget = JvmTarget.JVM_11
allWarningsAsErrors = warningsAsErrors.toBoolean() allWarningsAsErrors = warningsAsErrors
freeCompilerArgs.add( freeCompilerArgs.add(
// Enable experimental coroutines APIs, including Flow // Enable experimental coroutines APIs, including Flow
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",

@ -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.UserData
import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class UserNewsResourceTest { class UserNewsResourceTest {

@ -36,7 +36,7 @@ class TestNewsResourceDao : NewsResourceDao {
private val entitiesStateFlow = MutableStateFlow(emptyList<NewsResourceEntity>()) private val entitiesStateFlow = MutableStateFlow(emptyList<NewsResourceEntity>())
internal var topicCrossReferences: List<NewsResourceTopicCrossRef> = listOf() internal var topicCrossReferences: List<NewsResourceTopicCrossRef> = emptyList()
override fun getNewsResources( override fun getNewsResources(
useFilterTopicIds: Boolean, useFilterTopicIds: Boolean,

@ -49,5 +49,5 @@ fun NewsResourceEntity.asExternalModel() = NewsResource(
headerImageUrl = headerImageUrl, headerImageUrl = headerImageUrl,
publishDate = publishDate, publishDate = publishDate,
type = type, type = type,
topics = listOf(), topics = emptyList(),
) )

@ -45,7 +45,7 @@ fun NiaTopAppBar(
actionIcon: ImageVector, actionIcon: ImageVector,
actionIconContentDescription: String, actionIconContentDescription: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(), colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
onNavigationClick: () -> Unit = {}, onNavigationClick: () -> Unit = {},
onActionClick: () -> Unit = {}, onActionClick: () -> Unit = {},
) { ) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

@ -14,12 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import com.android.build.api.variant.BuildConfigField
import java.io.StringReader
import java.util.Properties
plugins { plugins {
alias(libs.plugins.nowinandroid.android.library) alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.jacoco) alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.nowinandroid.hilt) alias(libs.plugins.nowinandroid.hilt)
id("kotlinx-serialization") id("kotlinx-serialization")
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
} }
android { android {
@ -34,10 +37,6 @@ android {
} }
} }
secrets {
defaultPropertiesFileName = "secrets.defaults.properties"
}
dependencies { dependencies {
api(libs.kotlinx.datetime) api(libs.kotlinx.datetime)
api(projects.core.common) api(projects.core.common)
@ -52,3 +51,22 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test) 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)
})
}
}

@ -16,6 +16,7 @@
package com.google.samples.apps.nowinandroid.core.network.model package com.google.samples.apps.nowinandroid.core.network.model
import android.annotation.SuppressLint
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -23,6 +24,7 @@ import kotlinx.serialization.Serializable
/** /**
* Network representation of [NewsResource] when fetched from /newsresources * Network representation of [NewsResource] when fetched from /newsresources
*/ */
@SuppressLint("UnsafeOptInUsageError")
@Serializable @Serializable
data class NetworkNewsResource( data class NetworkNewsResource(
val id: String, val id: String,
@ -32,5 +34,5 @@ data class NetworkNewsResource(
val headerImageUrl: String, val headerImageUrl: String,
val publishDate: Instant, val publishDate: Instant,
val type: String, val type: String,
val topics: List<String> = listOf(), val topics: List<String> = emptyList(),
) )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

@ -38,7 +38,7 @@ import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent
fun InterestsRoute( fun InterestsRoute(
onTopicClick: (String) -> Unit, onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
highlightSelectedTopic: Boolean = false, shouldHighlightSelectedTopic: Boolean = false,
viewModel: InterestsViewModel = hiltViewModel(), viewModel: InterestsViewModel = hiltViewModel(),
) { ) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@ -50,7 +50,7 @@ fun InterestsRoute(
viewModel.onTopicClick(it) viewModel.onTopicClick(it)
onTopicClick(it) onTopicClick(it)
}, },
highlightSelectedTopic = highlightSelectedTopic, shouldHighlightSelectedTopic = shouldHighlightSelectedTopic,
modifier = modifier, modifier = modifier,
) )
} }
@ -61,7 +61,7 @@ internal fun InterestsScreen(
followTopic: (String, Boolean) -> Unit, followTopic: (String, Boolean) -> Unit,
onTopicClick: (String) -> Unit, onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
highlightSelectedTopic: Boolean = false, shouldHighlightSelectedTopic: Boolean = false,
) { ) {
Column( Column(
modifier = modifier, modifier = modifier,
@ -70,7 +70,6 @@ internal fun InterestsScreen(
when (uiState) { when (uiState) {
InterestsUiState.Loading -> InterestsUiState.Loading ->
NiaLoadingWheel( NiaLoadingWheel(
modifier = modifier,
contentDesc = stringResource(id = R.string.feature_interests_loading), contentDesc = stringResource(id = R.string.feature_interests_loading),
) )
@ -80,8 +79,7 @@ internal fun InterestsScreen(
onTopicClick = onTopicClick, onTopicClick = onTopicClick,
onFollowButtonClick = followTopic, onFollowButtonClick = followTopic,
selectedTopicId = uiState.selectedTopicId, selectedTopicId = uiState.selectedTopicId,
highlightSelectedTopic = highlightSelectedTopic, shouldHighlightSelectedTopic = shouldHighlightSelectedTopic,
modifier = modifier,
) )
is InterestsUiState.Empty -> InterestsEmptyScreen() is InterestsUiState.Empty -> InterestsEmptyScreen()

@ -49,7 +49,7 @@ fun TopicsTabContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
withBottomSpacer: Boolean = true, withBottomSpacer: Boolean = true,
selectedTopicId: String? = null, selectedTopicId: String? = null,
highlightSelectedTopic: Boolean = false, shouldHighlightSelectedTopic: Boolean = false,
) { ) {
Box( Box(
modifier = modifier modifier = modifier
@ -66,7 +66,7 @@ fun TopicsTabContent(
topics.forEach { followableTopic -> topics.forEach { followableTopic ->
val topicId = followableTopic.topic.id val topicId = followableTopic.topic.id
item(key = topicId) { item(key = topicId) {
val isSelected = highlightSelectedTopic && topicId == selectedTopicId val isSelected = shouldHighlightSelectedTopic && topicId == selectedTopicId
InterestsItem( InterestsItem(
name = followableTopic.topic.name, name = followableTopic.topic.name,
following = followableTopic.isFollowed, following = followableTopic.isFollowed,
@ -75,6 +75,7 @@ fun TopicsTabContent(
onClick = { onTopicClick(topicId) }, onClick = { onTopicClick(topicId) },
onFollowButtonClick = { onFollowButtonClick(topicId, it) }, onFollowButtonClick = { onFollowButtonClick(topicId, it) },
isSelected = isSelected, isSelected = isSelected,
modifier = Modifier.fillMaxWidth(),
) )
} }
} }

@ -16,10 +16,8 @@
package com.google.samples.apps.nowinandroid.feature.topic package com.google.samples.apps.nowinandroid.feature.topic
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.NewsResourceQuery
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository 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.model.data.UserNewsResource
import com.google.samples.apps.nowinandroid.core.result.Result import com.google.samples.apps.nowinandroid.core.result.Result
import com.google.samples.apps.nowinandroid.core.result.asResult 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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@ -38,18 +38,14 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel @HiltViewModel(assistedFactory = TopicViewModel.Factory::class)
class TopicViewModel @Inject constructor( class TopicViewModel @AssistedInject constructor(
savedStateHandle: SavedStateHandle,
private val userDataRepository: UserDataRepository, private val userDataRepository: UserDataRepository,
topicsRepository: TopicsRepository, topicsRepository: TopicsRepository,
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
@Assisted val topicId: String,
) : ViewModel() { ) : ViewModel() {
val topicId = savedStateHandle.toRoute<TopicRoute>().id
val topicUiState: StateFlow<TopicUiState> = topicUiState( val topicUiState: StateFlow<TopicUiState> = topicUiState(
topicId = topicId, topicId = topicId,
userDataRepository = userDataRepository, userDataRepository = userDataRepository,
@ -89,6 +85,13 @@ class TopicViewModel @Inject constructor(
userDataRepository.setNewsResourceViewed(newsResourceId, viewed) userDataRepository.setNewsResourceViewed(newsResourceId, viewed)
} }
} }
@AssistedFactory
interface Factory {
fun create(
topicId: String,
): TopicViewModel
}
} }
private fun topicUiState( private fun topicUiState(

@ -16,11 +16,14 @@
package com.google.samples.apps.nowinandroid.feature.topic.navigation package com.google.samples.apps.nowinandroid.feature.topic.navigation
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavOptionsBuilder
import androidx.navigation.compose.composable 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.TopicScreen
import com.google.samples.apps.nowinandroid.feature.topic.TopicViewModel
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable data class TopicRoute(val id: String) @Serializable data class TopicRoute(val id: String)
@ -36,11 +39,17 @@ fun NavGraphBuilder.topicScreen(
onBackClick: () -> Unit, onBackClick: () -> Unit,
onTopicClick: (String) -> Unit, onTopicClick: (String) -> Unit,
) { ) {
composable<TopicRoute> { composable<TopicRoute> { entry ->
val id = entry.toRoute<TopicRoute>().id
TopicScreen( TopicScreen(
showBackButton = showBackButton, showBackButton = showBackButton,
onBackClick = onBackClick, onBackClick = onBackClick,
onTopicClick = onTopicClick, onTopicClick = onTopicClick,
viewModel = hiltViewModel<TopicViewModel, TopicViewModel.Factory>(
key = id,
) { factory ->
factory.create(id)
},
) )
} }
} }

@ -16,8 +16,6 @@
package com.google.samples.apps.nowinandroid.feature.topic 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.data.repository.CompositeUserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource 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.TestTopicsRepository
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository 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.core.testing.util.MainDispatcherRule
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TopicRoute
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -37,22 +34,13 @@ import kotlinx.datetime.Instant
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs import kotlin.test.assertIs
/** /**
* To learn more about how this test handles Flows created with stateIn, see * To learn more about how this test handles Flows created with stateIn, see
* https://developer.android.com/kotlin/flow/test#statein * 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 { class TopicViewModelTest {
@get:Rule @get:Rule
@ -70,12 +58,10 @@ class TopicViewModelTest {
@Before @Before
fun setup() { fun setup() {
viewModel = TopicViewModel( viewModel = TopicViewModel(
savedStateHandle = SavedStateHandle(
route = TopicRoute(id = testInputTopics[0].topic.id),
),
userDataRepository = userDataRepository, userDataRepository = userDataRepository,
topicsRepository = topicsRepository, topicsRepository = topicsRepository,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
topicId = testInputTopics[0].topic.id,
) )
} }

@ -2,13 +2,14 @@
accompanist = "0.37.0" accompanist = "0.37.0"
androidDesugarJdkLibs = "2.1.4" androidDesugarJdkLibs = "2.1.4"
# AGP and tools should be updated together # AGP and tools should be updated together
androidGradlePlugin = "8.7.3" androidGradlePlugin = "8.9.0"
androidTools = "31.7.3" androidTools = "31.9.0"
androidxActivity = "1.9.3" androidxActivity = "1.9.3"
androidxAppCompat = "1.7.0" androidxAppCompat = "1.7.0"
androidxBrowser = "1.8.0" androidxBrowser = "1.8.0"
androidxComposeBom = "2024.12.01" androidxComposeBom = "2025.02.00"
androidxComposeFoundation = "1.8.0-alpha07" androidxComposeFoundation = "1.8.0-alpha07"
androidxComposeMaterial3Adaptive = "1.1.0-rc01"
androidxComposeRuntimeTracing = "1.7.6" androidxComposeRuntimeTracing = "1.7.6"
androidxCore = "1.15.0" androidxCore = "1.15.0"
androidxCoreSplashscreen = "1.0.1" androidxCoreSplashscreen = "1.0.1"
@ -17,14 +18,14 @@ androidxEspresso = "3.6.1"
androidxHiltNavigationCompose = "1.2.0" androidxHiltNavigationCompose = "1.2.0"
androidxLifecycle = "2.8.7" androidxLifecycle = "2.8.7"
androidxLintGradle = "1.0.0-alpha03" androidxLintGradle = "1.0.0-alpha03"
androidxMacroBenchmark = "1.3.3" androidxMacroBenchmark = "1.3.4"
androidxMetrics = "1.0.0-beta01" androidxMetrics = "1.0.0-beta01"
androidxNavigation = "2.8.5" androidxNavigation = "2.8.5"
androidxProfileinstaller = "1.4.1" androidxProfileinstaller = "1.4.1"
androidxTestCore = "1.6.1" androidxTestCore = "1.7.0-rc01"
androidxTestExt = "1.2.1" androidxTestExt = "1.3.0-rc01"
androidxTestRules = "1.6.1" androidxTestRules = "1.7.0-rc01"
androidxTestRunner = "1.6.2" androidxTestRunner = "1.7.0-rc01"
androidxTracing = "1.3.0-alpha02" androidxTracing = "1.3.0-alpha02"
androidxUiAutomator = "2.3.0" androidxUiAutomator = "2.3.0"
androidxWindowManager = "1.3.0" androidxWindowManager = "1.3.0"
@ -37,15 +38,15 @@ firebasePerfPlugin = "1.4.2"
gmsPlugin = "4.4.2" gmsPlugin = "4.4.2"
googleOss = "17.1.0" googleOss = "17.1.0"
googleOssPlugin = "0.10.6" googleOssPlugin = "0.10.6"
hilt = "2.54" hilt = "2.56"
hiltExt = "1.2.0" hiltExt = "1.2.0"
jacoco = "0.8.12" jacoco = "0.8.12"
junit4 = "4.13.2" junit4 = "4.13.2"
kotlin = "2.1.0" kotlin = "2.1.10"
kotlinxCoroutines = "1.10.1" kotlinxCoroutines = "1.10.1"
kotlinxDatetime = "0.6.1" kotlinxDatetime = "0.6.1"
kotlinxSerializationJson = "1.8.0" kotlinxSerializationJson = "1.8.0"
ksp = "2.1.0-1.0.29" ksp = "2.1.10-1.0.31"
moduleGraph = "2.7.1" moduleGraph = "2.7.1"
okhttp = "4.12.0" okhttp = "4.12.0"
protobuf = "4.29.2" protobuf = "4.29.2"
@ -54,7 +55,7 @@ retrofit = "2.11.0"
retrofitKotlinxSerializationJson = "1.0.0" retrofitKotlinxSerializationJson = "1.0.0"
robolectric = "4.14.1" robolectric = "4.14.1"
roborazzi = "1.39.0" roborazzi = "1.39.0"
room = "2.6.1" room = "2.7.2"
secrets = "2.0.1" secrets = "2.0.1"
truth = "1.4.4" truth = "1.4.4"
turbine = "1.2.0" 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-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" }
androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidxBrowser" } 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 = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxComposeFoundation" }
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } 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-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } 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-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 = { group = "androidx.compose.material3.adaptive", name = "adaptive", version.ref = "androidxComposeMaterial3Adaptive" }
androidx-compose-material3-adaptive-layout = { group = "androidx.compose.material3.adaptive", name = "adaptive-layout" } 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" } 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-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" } androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" }

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored

@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # 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 APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum

@ -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"
Loading…
Cancel
Save