Merge branch 'android-remote-main' into avd-cache

Change-Id: I1a5a79cff18429ad92c276da2586ac82622c80d6
pull/1819/head
Jaehwa Noh 2 months ago
commit 2248f48dab

@ -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
@ -73,6 +77,28 @@ jobs:
disable_globbing: true
commit_message: "🤖 Updates baselines for Dependency Guard"
- name: Update Graphs
run: ./gradlew graphUpdate
- name: Check Graphs
id: graphs_verify
run: git add -- '**/README.md' && git diff --cached --quiet --exit-code -- '**/README.md'
- name: Prevent updating graphs if this is a fork
id: checkfork_graphs
continue-on-error: false
if: steps.graphs_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
run: |
echo "::error::Check Graphs failed, please update graphs with: ./gradlew graphUpdate" && exit 1
- name: Push new graphs if available
if: steps.graphs_verify.outcome == 'failure' && github.event_name == 'pull_request'
uses: stefanzweifel/git-auto-commit-action@v5
with:
file_pattern: '**/README.md'
disable_globbing: true
commit_message: "🤖 Updates graphs"
- name: Run all local screenshot tests (Roborazzi)
id: screenshotsverify
continue-on-error: true
@ -107,7 +133,7 @@ jobs:
run: ./gradlew testDemoDebug :lint:test
- name: Build all build type and flavor permutations
run: ./gradlew :app:assemble
run: ./gradlew :app:assemble -PminifyWithR8=false
- name: Upload build outputs (APKs)
uses: actions/upload-artifact@v4
@ -139,11 +165,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: './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: './'
sarif_file: './lint/'
category: lint
- name: Check badging
run: ./gradlew :app:checkProdReleaseBadging
@ -180,16 +221,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: AVD cache
uses: actions/cache@v4

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

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

@ -0,0 +1 @@
AGENTS.md

@ -0,0 +1,58 @@
# 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 `Flow`s. `ViewModel`s 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`, and two build types: `debug` and `release`.
- 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 the `:app` module and they can start activities like `MainActivity`.
#### Local tests
- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) for most assertions
- [cashapp/turbine](https://github.com/cashapp/turbine) for complex coroutine tests
- [google/truth](https://github.com/google/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 https://github.com/android/nowinandroid.

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

@ -1,3 +1,58 @@
# :app-nia-catalog module
## Dependency graph
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.svg)
# `:app-nia-catalog`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:designsystem[designsystem]:::android-library
:core:model[model]:::jvm-library
:core:ui[ui]:::android-library
end
:app-nia-catalog[app-nia-catalog]:::android-application
:app-nia-catalog -.-> :core:designsystem
:app-nia-catalog -.-> :core:ui
:core:ui --> :core:analytics
:core:ui --> :core:designsystem
:core:ui --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,7 +1,7 @@
androidx.activity:activity-compose:1.9.3
androidx.activity:activity-ktx:1.9.3
androidx.activity:activity:1.9.3
androidx.annotation:annotation-experimental:1.4.1
androidx.annotation:annotation-experimental:1.5.1
androidx.annotation:annotation-jvm:1.9.1
androidx.annotation:annotation:1.9.1
androidx.appcompat:appcompat-resources:1.6.1
@ -9,97 +9,111 @@ 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
androidx.collection:collection-ktx:1.5.0
androidx.collection:collection:1.5.0
androidx.compose.animation:animation-android:1.10.0-alpha02
androidx.compose.animation:animation-core-android:1.10.0-alpha02
androidx.compose.animation:animation-core:1.10.0-alpha02
androidx.compose.animation:animation:1.10.0-alpha02
androidx.compose.foundation:foundation-android:1.10.0-alpha02
androidx.compose.foundation:foundation-layout-android:1.10.0-alpha02
androidx.compose.foundation:foundation-layout:1.10.0-alpha02
androidx.compose.foundation:foundation:1.10.0-alpha02
androidx.compose.material3.adaptive:adaptive-android:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive:1.2.0-beta01
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.5.0-alpha03
androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha03
androidx.compose.material3:material3-android:1.5.0-alpha03
androidx.compose.material3:material3:1.5.0-alpha03
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.10.0-alpha02
androidx.compose.material:material-ripple:1.10.0-alpha02
androidx.compose.runtime:runtime-android:1.10.0-alpha02
androidx.compose.runtime:runtime-annotation-android:1.10.0-alpha02
androidx.compose.runtime:runtime-annotation:1.10.0-alpha02
androidx.compose.runtime:runtime-saveable-android:1.10.0-alpha02
androidx.compose.runtime:runtime-saveable:1.10.0-alpha02
androidx.compose.runtime:runtime:1.10.0-alpha02
androidx.compose.ui:ui-android:1.10.0-alpha02
androidx.compose.ui:ui-geometry-android:1.10.0-alpha02
androidx.compose.ui:ui-geometry:1.10.0-alpha02
androidx.compose.ui:ui-graphics-android:1.10.0-alpha02
androidx.compose.ui:ui-graphics:1.10.0-alpha02
androidx.compose.ui:ui-text-android:1.10.0-alpha02
androidx.compose.ui:ui-text:1.10.0-alpha02
androidx.compose.ui:ui-tooling-preview-android:1.10.0-alpha02
androidx.compose.ui:ui-tooling-preview:1.10.0-alpha02
androidx.compose.ui:ui-unit-android:1.10.0-alpha02
androidx.compose.ui:ui-unit:1.10.0-alpha02
androidx.compose.ui:ui-util-android:1.10.0-alpha02
androidx.compose.ui:ui-util:1.10.0-alpha02
androidx.compose.ui:ui:1.10.0-alpha02
androidx.compose:compose-bom-alpha:2025.08.01
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.13.1
androidx.core:core:1.13.1
androidx.core:core-ktx:1.16.0
androidx.core:core-viewtree:1.0.0
androidx.core:core:1.16.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
androidx.documentfile:documentfile:1.0.0
androidx.dynamicanimation:dynamicanimation:1.0.0
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
androidx.lifecycle:lifecycle-common:2.8.7
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7
androidx.lifecycle:lifecycle-livedata-core:2.8.7
androidx.lifecycle:lifecycle-livedata:2.8.7
androidx.lifecycle:lifecycle-process:2.8.7
androidx.lifecycle:lifecycle-runtime-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
androidx.lifecycle:lifecycle-runtime:2.8.7
androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
androidx.lifecycle:lifecycle-viewmodel:2.8.7
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.10.0-alpha03
androidx.lifecycle:lifecycle-common-jvm:2.10.0-alpha03
androidx.lifecycle:lifecycle-common:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata-core:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata:2.10.0-alpha03
androidx.lifecycle:lifecycle-process:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-compose-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-compose:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel:2.10.0-alpha03
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01
androidx.print:print:1.0.0
androidx.profileinstaller:profileinstaller:1.4.0
androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1
androidx.savedstate:savedstate-android:1.4.0-alpha03
androidx.savedstate:savedstate-compose-android:1.4.0-alpha03
androidx.savedstate:savedstate-compose:1.4.0-alpha03
androidx.savedstate:savedstate-ktx:1.4.0-alpha03
androidx.savedstate:savedstate:1.4.0-alpha03
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.3.0-alpha02
androidx.tracing:tracing:1.3.0-alpha02
androidx.transition:transition:1.6.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
androidx.window.extensions.core:core:1.0.0
androidx.window:window-core-android:1.3.0
androidx.window:window-core:1.3.0
androidx.window:window:1.3.0
androidx.window:window-core-android:1.4.0
androidx.window:window-core:1.4.0
androidx.window:window:1.4.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.57.2
com.google.dagger:dagger:2.57.2
com.google.dagger:hilt-android:2.57.2
com.google.dagger:hilt-core:2.57.2
com.google.guava:listenablefuture:1.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.0
@ -110,15 +124,18 @@ 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.2.21
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.2.21
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
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1
org.jetbrains.kotlinx:kotlinx-datetime:0.6.1
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3
org.jetbrains:annotations:23.0.0
org.jspecify:jspecify:1.0.0

@ -1,3 +1,134 @@
# :app module
## Dependency graph
![Dependency graph](../docs/images/graphs/dep_graph_app.svg)
# `:app`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:designsystem[designsystem]:::android-library
:core:domain[domain]:::android-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
:core:ui[ui]:::android-library
end
subgraph :feature
direction TB
:feature:bookmarks[bookmarks]:::android-feature
:feature:foryou[foryou]:::android-feature
:feature:interests[interests]:::android-feature
:feature:search[search]:::android-feature
:feature:settings[settings]:::android-feature
:feature:topic[topic]:::android-feature
end
subgraph :sync
direction TB
:sync:work[work]:::android-library
end
:benchmarks[benchmarks]:::android-test
:app[app]:::android-application
:app -.->|baselineProfile| :benchmarks
:app -.-> :core:analytics
:app -.-> :core:common
:app -.-> :core:data
:app -.-> :core:designsystem
:app -.-> :core:model
:app -.-> :core:ui
:app -.-> :feature:bookmarks
:app -.-> :feature:foryou
:app -.-> :feature:interests
:app -.-> :feature:search
:app -.-> :feature:settings
:app -.-> :feature:topic
:app -.-> :sync:work
:benchmarks -.->|testedApks| :app
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data --> :core:database
:core:data --> :core:datastore
:core:data --> :core:network
:core:data -.-> :core:notifications
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:domain --> :core:data
:core:domain --> :core:model
:core:network --> :core:common
:core:network --> :core:model
:core:notifications -.-> :core:common
:core:notifications --> :core:model
:core:ui --> :core:analytics
:core:ui --> :core:designsystem
:core:ui --> :core:model
:feature:bookmarks -.-> :core:data
:feature:bookmarks -.-> :core:designsystem
:feature:bookmarks -.-> :core:ui
:feature:foryou -.-> :core:data
:feature:foryou -.-> :core:designsystem
:feature:foryou -.-> :core:domain
:feature:foryou -.-> :core:notifications
:feature:foryou -.-> :core:ui
:feature:interests -.-> :core:data
:feature:interests -.-> :core:designsystem
:feature:interests -.-> :core:domain
:feature:interests -.-> :core:ui
:feature:search -.-> :core:data
:feature:search -.-> :core:designsystem
:feature:search -.-> :core:domain
:feature:search -.-> :core:ui
:feature:settings -.-> :core:data
:feature:settings -.-> :core:designsystem
:feature:settings -.-> :core:ui
:feature:topic -.-> :core:data
:feature:topic -.-> :core:designsystem
:feature:topic -.-> :core:ui
:sync:work -.-> :core:analytics
:sync:work -.-> :core:data
:sync:work -.-> :core:notifications
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -22,7 +22,7 @@ plugins {
alias(libs.plugins.nowinandroid.android.application.jacoco)
alias(libs.plugins.nowinandroid.android.application.firebase)
alias(libs.plugins.nowinandroid.hilt)
id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.google.osslicenses)
alias(libs.plugins.baselineprofile)
alias(libs.plugins.roborazzi)
alias(libs.plugins.kotlin.serialization)
@ -43,9 +43,11 @@ android {
applicationIdSuffix = NiaBuildType.DEBUG.applicationIdSuffix
}
release {
isMinifyEnabled = true
isMinifyEnabled = providers.gradleProperty("minifyWithR8")
.map(String::toBooleanStrict).getOrElse(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.
@ -61,11 +63,7 @@ android {
excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
testOptions.unitTests.isIncludeAndroidResources = true
namespace = "com.google.samples.apps.nowinandroid"
}
@ -86,6 +84,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)

@ -1,7 +1,7 @@
androidx.activity:activity-compose:1.9.3
androidx.activity:activity-ktx:1.9.3
androidx.activity:activity:1.9.3
androidx.annotation:annotation-experimental:1.4.1
androidx.activity:activity-compose:1.10.1
androidx.activity:activity-ktx:1.10.1
androidx.activity:activity:1.10.1
androidx.annotation:annotation-experimental:1.5.1
androidx.annotation:annotation-jvm:1.9.1
androidx.annotation:annotation:1.9.1
androidx.appcompat:appcompat-resources:1.7.0
@ -10,60 +10,63 @@ 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
androidx.collection:collection-ktx:1.5.0
androidx.collection:collection:1.5.0
androidx.compose.animation:animation-android:1.10.0-alpha02
androidx.compose.animation:animation-core-android:1.10.0-alpha02
androidx.compose.animation:animation-core:1.10.0-alpha02
androidx.compose.animation:animation:1.10.0-alpha02
androidx.compose.foundation:foundation-android:1.10.0-alpha02
androidx.compose.foundation:foundation-layout-android:1.10.0-alpha02
androidx.compose.foundation:foundation-layout:1.10.0-alpha02
androidx.compose.foundation:foundation:1.10.0-alpha02
androidx.compose.material3.adaptive:adaptive-android:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive-layout-android:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive-layout:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive-navigation-android:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive-navigation:1.2.0-beta01
androidx.compose.material3.adaptive:adaptive:1.2.0-beta01
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.5.0-alpha03
androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha03
androidx.compose.material3:material3-android:1.5.0-alpha03
androidx.compose.material3:material3-window-size-class-android:1.5.0-alpha03
androidx.compose.material3:material3-window-size-class:1.5.0-alpha03
androidx.compose.material3:material3:1.5.0-alpha03
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.10.0-alpha02
androidx.compose.material:material-ripple:1.10.0-alpha02
androidx.compose.runtime:runtime-android:1.10.0-alpha02
androidx.compose.runtime:runtime-annotation-android:1.10.0-alpha02
androidx.compose.runtime:runtime-annotation:1.10.0-alpha02
androidx.compose.runtime:runtime-saveable-android:1.10.0-alpha02
androidx.compose.runtime:runtime-saveable:1.10.0-alpha02
androidx.compose.runtime:runtime-tracing:1.10.0-alpha02
androidx.compose.runtime:runtime:1.10.0-alpha02
androidx.compose.ui:ui-android:1.10.0-alpha02
androidx.compose.ui:ui-geometry-android:1.10.0-alpha02
androidx.compose.ui:ui-geometry:1.10.0-alpha02
androidx.compose.ui:ui-graphics-android:1.10.0-alpha02
androidx.compose.ui:ui-graphics:1.10.0-alpha02
androidx.compose.ui:ui-text-android:1.10.0-alpha02
androidx.compose.ui:ui-text:1.10.0-alpha02
androidx.compose.ui:ui-tooling-preview-android:1.10.0-alpha02
androidx.compose.ui:ui-tooling-preview:1.10.0-alpha02
androidx.compose.ui:ui-unit-android:1.10.0-alpha02
androidx.compose.ui:ui-unit:1.10.0-alpha02
androidx.compose.ui:ui-util-android:1.10.0-alpha02
androidx.compose.ui:ui-util:1.10.0-alpha02
androidx.compose.ui:ui:1.10.0-alpha02
androidx.compose:compose-bom-alpha:2025.08.01
androidx.concurrent:concurrent-futures-ktx:1.1.0
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.15.0
androidx.core:core-ktx:1.16.0
androidx.core:core-splashscreen:1.0.1
androidx.core:core:1.15.0
androidx.core:core-viewtree:1.0.0
androidx.core:core:1.16.0
androidx.cursoradapter:cursoradapter:1.0.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
@ -79,37 +82,41 @@ androidx.datastore:datastore-preferences:1.1.1
androidx.datastore:datastore:1.1.1
androidx.documentfile:documentfile:1.0.0
androidx.drawerlayout:drawerlayout:1.0.0
androidx.dynamicanimation:dynamicanimation:1.0.0
androidx.emoji2:emoji2-views-helper:1.4.0
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
androidx.hilt:hilt-work:1.2.0
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.8.7
androidx.lifecycle:lifecycle-common-jvm:2.8.7
androidx.lifecycle:lifecycle-common:2.8.7
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7
androidx.lifecycle:lifecycle-livedata-core:2.8.7
androidx.lifecycle:lifecycle-livedata:2.8.7
androidx.lifecycle:lifecycle-process:2.8.7
androidx.lifecycle:lifecycle-runtime-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
androidx.lifecycle:lifecycle-runtime-compose:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
androidx.lifecycle:lifecycle-runtime:2.8.7
androidx.lifecycle:lifecycle-service:2.8.7
androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.7
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
androidx.lifecycle:lifecycle-viewmodel:2.8.7
androidx.lifecycle:lifecycle-common-java8:2.10.0-alpha03
androidx.lifecycle:lifecycle-common-jvm:2.10.0-alpha03
androidx.lifecycle:lifecycle-common:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata-core:2.10.0-alpha03
androidx.lifecycle:lifecycle-livedata:2.10.0-alpha03
androidx.lifecycle:lifecycle-process:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-compose-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-compose:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-runtime:2.10.0-alpha03
androidx.lifecycle:lifecycle-service:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0-alpha03
androidx.lifecycle:lifecycle-viewmodel:2.10.0-alpha03
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01
@ -123,25 +130,32 @@ 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.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.room:room-common-jvm:2.8.3
androidx.room:room-common:2.8.3
androidx.room:room-ktx:2.8.3
androidx.room:room-runtime-android:2.8.3
androidx.room:room-runtime:2.8.3
androidx.savedstate:savedstate-android:1.4.0-alpha03
androidx.savedstate:savedstate-compose-android:1.4.0-alpha03
androidx.savedstate:savedstate-compose:1.4.0-alpha03
androidx.savedstate:savedstate-ktx:1.4.0-alpha03
androidx.savedstate:savedstate:1.4.0-alpha03
androidx.sqlite:sqlite-android:2.6.1
androidx.sqlite:sqlite-framework-android:2.6.1
androidx.sqlite:sqlite-framework:2.6.1
androidx.sqlite:sqlite:2.6.1
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.3.0-alpha02
androidx.tracing:tracing-perfetto:1.0.0
androidx.tracing:tracing:1.3.0-alpha02
androidx.transition:transition:1.6.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
androidx.window.extensions.core:core:1.0.0
androidx.window:window-core-android:1.3.0
androidx.window:window-core:1.3.0
androidx.window:window:1.3.0
androidx.window:window-core-android:1.4.0
androidx.window:window-core:1.4.0
androidx.window:window:1.4.0
androidx.work:work-runtime-ktx:2.10.0
androidx.work:work-runtime:2.10.0
com.caverock:androidsvg-aar:1.4
@ -164,10 +178,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.57.2
com.google.dagger:dagger:2.57.2
com.google.dagger:hilt-android:2.57.2
com.google.dagger:hilt-core:2.57.2
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 +226,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.2.21
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.2.21
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

@ -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:'23'
targetSdkVersion:'35'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
@ -105,9 +105,9 @@ application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Now in Android' icon='res/mipmap-anydpi-v26/ic_launcher.xml'
launchable-activity: name='com.google.samples.apps.nowinandroid.MainActivity' label='' icon=''
uses-library-not-required:'android.ext.adservices'
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
uses-library-not-required:'android.ext.adservices'
feature-group: label=''
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'

@ -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() {
if (isDebuggable()) {
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.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() },

@ -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<InterestsRoute> {
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<DetailPaneNavHostRoute>()
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<TopicPlaceholderRoute> {
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<TopicViewModel, TopicViewModel.Factory>(
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,
)
},
)
}

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: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 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: 185 KiB

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

@ -0,0 +1,134 @@
# `:benchmarks`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:designsystem[designsystem]:::android-library
:core:domain[domain]:::android-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
:core:ui[ui]:::android-library
end
subgraph :feature
direction TB
:feature:bookmarks[bookmarks]:::android-feature
:feature:foryou[foryou]:::android-feature
:feature:interests[interests]:::android-feature
:feature:search[search]:::android-feature
:feature:settings[settings]:::android-feature
:feature:topic[topic]:::android-feature
end
subgraph :sync
direction TB
:sync:work[work]:::android-library
end
:benchmarks[benchmarks]:::android-test
:app[app]:::android-application
:app -.->|baselineProfile| :benchmarks
:app -.-> :core:analytics
:app -.-> :core:common
:app -.-> :core:data
:app -.-> :core:designsystem
:app -.-> :core:model
:app -.-> :core:ui
:app -.-> :feature:bookmarks
:app -.-> :feature:foryou
:app -.-> :feature:interests
:app -.-> :feature:search
:app -.-> :feature:settings
:app -.-> :feature:topic
:app -.-> :sync:work
:benchmarks -.->|testedApks| :app
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data --> :core:database
:core:data --> :core:datastore
:core:data --> :core:network
:core:data -.-> :core:notifications
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:domain --> :core:data
:core:domain --> :core:model
:core:network --> :core:common
:core:network --> :core:model
:core:notifications -.-> :core:common
:core:notifications --> :core:model
:core:ui --> :core:analytics
:core:ui --> :core:designsystem
:core:ui --> :core:model
:feature:bookmarks -.-> :core:data
:feature:bookmarks -.-> :core:designsystem
:feature:bookmarks -.-> :core:ui
:feature:foryou -.-> :core:data
:feature:foryou -.-> :core:designsystem
:feature:foryou -.-> :core:domain
:feature:foryou -.-> :core:notifications
:feature:foryou -.-> :core:ui
:feature:interests -.-> :core:data
:feature:interests -.-> :core:designsystem
:feature:interests -.-> :core:domain
:feature:interests -.-> :core:ui
:feature:search -.-> :core:data
:feature:search -.-> :core:designsystem
:feature:search -.-> :core:domain
:feature:search -.-> :core:ui
:feature:settings -.-> :core:data
:feature:settings -.-> :core:designsystem
:feature:settings -.-> :core:ui
:feature:topic -.-> :core:data
:feature:topic -.-> :core:designsystem
:feature:topic -.-> :core:ui
:sync:work -.-> :core:analytics
:sync:work -.-> :core:data
:sync:work -.-> :core:notifications
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -45,8 +45,8 @@ android {
)
}
testOptions.managedDevices.devices {
create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel6Api33") {
testOptions.managedDevices.localDevices {
create("pixel6Api33") {
device = "Pixel 6"
apiLevel = 33
systemImageSource = "aosp"

@ -37,7 +37,7 @@ kotlin {
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.android.gradleApiPlugin)
compileOnly(libs.android.tools.common)
compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.firebase.crashlytics.gradlePlugin)
@ -114,5 +114,9 @@ gradlePlugin {
id = libs.plugins.nowinandroid.jvm.library.get().pluginId
implementationClass = "JvmLibraryConventionPlugin"
}
register("root") {
id = libs.plugins.nowinandroid.root.get().pluginId
implementationClass = "RootPlugin"
}
}
}

@ -16,7 +16,6 @@
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.google.samples.apps.nowinandroid.configureBadgingTasks
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
@ -44,7 +43,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
}
extensions.configure<ApplicationAndroidComponentsExtension> {
configurePrintApksTask(this)
configureBadgingTasks(extensions.getByType<BaseExtension>(), this)
configureBadgingTasks(extensions.getByType<ApplicationExtension>(), this)
}
}
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
import com.android.build.gradle.LibraryExtension
import com.android.build.api.dsl.LibraryExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin

@ -14,7 +14,7 @@
* limitations under the License.
*/
import com.android.build.gradle.LibraryExtension
import com.android.build.api.dsl.LibraryExtension
import com.google.samples.apps.nowinandroid.configureAndroidCompose
import org.gradle.api.Plugin
import org.gradle.api.Project

@ -14,8 +14,8 @@
* limitations under the License.
*/
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.LibraryExtension
import com.google.samples.apps.nowinandroid.configureFlavors
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
@ -37,7 +37,8 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = 35
testOptions.targetSdk = 35
lint.targetSdk = 35
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testOptions.animationsDisabled = true
configureFlavors(this)

@ -14,7 +14,7 @@
* limitations under the License.
*/
import com.android.build.gradle.TestExtension
import com.android.build.api.dsl.TestExtension
import com.google.samples.apps.nowinandroid.configureGradleManagedDevices
import com.google.samples.apps.nowinandroid.configureKotlinAndroid
import org.gradle.api.Plugin

@ -0,0 +1,26 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.google.samples.apps.nowinandroid.configureGraphTasks
import org.gradle.api.Plugin
import org.gradle.api.Project
class RootPlugin : Plugin<Project> {
override fun apply(target: Project) {
require(target.path == ":")
target.subprojects { configureGraphTasks() }
}
}

@ -42,13 +42,6 @@ internal fun Project.configureAndroidCompose(
"implementation"(libs.findLibrary("androidx-compose-ui-tooling-preview").get())
"debugImplementation"(libs.findLibrary("androidx-compose-ui-tooling").get())
}
testOptions {
unitTests {
// For Robolectric
isIncludeAndroidResources = true
}
}
}
extensions.configure<ComposeCompilerGradlePluginExtension> {

@ -18,8 +18,8 @@ package com.google.samples.apps.nowinandroid
import com.android.SdkConstants
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.google.common.truth.Truth.assertWithMessage
import org.gradle.api.DefaultTask
import org.gradle.api.Project
@ -110,7 +110,7 @@ private fun String.capitalized() = replaceFirstChar {
}
fun Project.configureBadgingTasks(
baseExtension: BaseExtension,
baseExtension: ApplicationExtension,
componentsExtension: ApplicationAndroidComponentsExtension,
) {
// Registers a callback to be called, when a new variant is configured

@ -0,0 +1,327 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid
import com.android.utils.associateWithNotNull
import com.google.samples.apps.nowinandroid.PluginType.Unknown
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity.NONE
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import kotlin.text.RegexOption.DOT_MATCHES_ALL
/**
* Generates module dependency graphs with `graphDump` task, and update the corresponding `README.md` file with `graphUpdate`.
*
* This is not an optimal implementation and could be improved if needed:
* - [Graph.invoke] is **recursively** searching through dependent projects (although in practice it will never reach a stack overflow).
* - [Graph.invoke] is entirely re-executed for all projects, without re-using intermediate values.
* - [Graph.invoke] is always executed during Gradle's Configuration phase (but takes in general less than 1 ms for a project).
*
* The resulting graphs can be configured with `graph.ignoredProjects` and `graph.supportedConfigurations` properties.
*/
private class Graph(
private val root: Project,
private val dependencies: MutableMap<Project, Set<Pair<Configuration, Project>>> = mutableMapOf(),
private val plugins: MutableMap<Project, PluginType> = mutableMapOf(),
private val seen: MutableSet<String> = mutableSetOf(),
) {
private val ignoredProjects = root.providers.gradleProperty("graph.ignoredProjects")
.map { it.split(",").toSet() }
.orElse(emptySet())
private val supportedConfigurations =
root.providers.gradleProperty("graph.supportedConfigurations")
.map { it.split(",").toSet() }
.orElse(setOf("api", "implementation", "baselineProfile", "testedApks"))
operator fun invoke(project: Project = root): Graph {
if (project.path in seen) return this
seen += project.path
plugins.putIfAbsent(
project,
PluginType.entries.firstOrNull { project.pluginManager.hasPlugin(it.id) } ?: Unknown,
)
dependencies.compute(project) { _, u -> u.orEmpty() }
project.configurations
.matching { it.name in supportedConfigurations.get() }
.associateWithNotNull { it.dependencies.withType<ProjectDependency>().ifEmpty { null } }
.flatMap { (c, value) -> value.map { dep -> c to project.project(dep.path) } }
.filter { (_, p) -> p.path !in ignoredProjects.get() }
.forEach { (configuration: Configuration, projectDependency: Project) ->
dependencies.compute(project) { _, u -> u.orEmpty() + (configuration to projectDependency) }
invoke(projectDependency)
}
return this
}
fun dependencies(): Map<String, Set<Pair<String, String>>> = dependencies
.mapKeys { it.key.path }
.mapValues { it.value.mapTo(mutableSetOf()) { (c, p) -> c.name to p.path } }
fun plugins() = plugins.mapKeys { it.key.path }
}
/**
* Declaration order is important, as only the first match will be retained.
*/
internal enum class PluginType(val id: String, val ref: String, val style: String) {
AndroidApplication(
id = "nowinandroid.android.application",
ref = "android-application",
style = "fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000",
),
AndroidFeature(
id = "nowinandroid.android.feature",
ref = "android-feature",
style = "fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000",
),
AndroidLibrary(
id = "nowinandroid.android.library",
ref = "android-library",
style = "fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000",
),
AndroidTest(
id = "nowinandroid.android.test",
ref = "android-test",
style = "fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000",
),
Jvm(
id = "nowinandroid.jvm.library",
ref = "jvm-library",
style = "fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000",
),
Unknown(
id = "?",
ref = "unknown",
style = "fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000",
),
}
internal fun Project.configureGraphTasks() {
if (!buildFile.exists()) return // Ignore root modules without build file
val dumpTask = tasks.register<GraphDumpTask>("graphDump") {
val graph = Graph(this@configureGraphTasks).invoke()
projectPath = this@configureGraphTasks.path
dependencies = graph.dependencies()
plugins = graph.plugins()
output = this@configureGraphTasks.layout.buildDirectory.file("mermaid/graph.txt")
legend = this@configureGraphTasks.layout.buildDirectory.file("mermaid/legend.txt")
}
tasks.register<GraphUpdateTask>("graphUpdate") {
projectPath = this@configureGraphTasks.path
input = dumpTask.flatMap { it.output }
legend = dumpTask.flatMap { it.legend }
output = this@configureGraphTasks.layout.projectDirectory.file("README.md")
}
}
@CacheableTask
private abstract class GraphDumpTask : DefaultTask() {
@get:Input
abstract val projectPath: Property<String>
@get:Input
abstract val dependencies: MapProperty<String, Set<Pair<String, String>>>
@get:Input
abstract val plugins: MapProperty<String, PluginType>
@get:OutputFile
abstract val output: RegularFileProperty
@get:OutputFile
abstract val legend: RegularFileProperty
override fun getDescription() = "Dumps project dependencies to a mermaid file."
@TaskAction
operator fun invoke() {
output.get().asFile.writeText(mermaid())
legend.get().asFile.writeText(legend())
logger.lifecycle(output.get().asFile.toPath().toUri().toString())
}
private fun mermaid() = buildString {
val dependencies: Set<Dependency> = dependencies.get()
.flatMapTo(mutableSetOf()) { (project, entries) -> entries.map { it.toDependency(project) } }
// FrontMatter configuration (not supported yet on GitHub.com)
appendLine(
// language=YAML
"""
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
""".trimIndent(),
)
// Graph declaration
appendLine("graph TB")
// Nodes and subgraphs (limited to a single nested layer)
val (rootProjects, nestedProjects) = dependencies
.map { listOf(it.project, it.dependency) }.flatten().toSet()
.plus(projectPath.get()) // Special case when this specific module has no other dependency
.groupBy { it.substringBeforeLast(":") }
.entries.partition { it.key.isEmpty() }
nestedProjects.sortedByDescending { it.value.size }.forEach { (group, projects) ->
appendLine(" subgraph $group")
appendLine(" direction TB")
projects.sorted().forEach {
appendLine(it.alias(indent = 4, plugins.get().getValue(it)))
}
appendLine(" end")
}
rootProjects.flatMap { it.value }.sortedDescending().forEach {
appendLine(it.alias(indent = 2, plugins.get().getValue(it)))
}
// Links
if (dependencies.isNotEmpty()) appendLine()
dependencies
.sortedWith(compareBy({ it.project }, { it.dependency }, { it.configuration }))
.forEach { appendLine(it.link(indent = 2)) }
// Classes
appendLine()
PluginType.entries.forEach { appendLine(it.classDef()) }
}
private fun legend() = buildString {
appendLine("graph TB")
listOf(
"application" to PluginType.AndroidApplication,
"feature" to PluginType.AndroidFeature,
"library" to PluginType.AndroidLibrary,
"jvm" to PluginType.Jvm,
).forEach { (name, type) ->
appendLine(name.alias(indent = 2, type))
}
appendLine()
listOf(
Dependency("application", "implementation", "feature"),
Dependency("library", "api", "jvm"),
).forEach {
appendLine(it.link(indent = 2))
}
appendLine()
PluginType.entries.forEach { appendLine(it.classDef()) }
}
private class Dependency(val project: String, val configuration: String, val dependency: String)
private fun Pair<String, String>.toDependency(project: String) =
Dependency(project, configuration = first, dependency = second)
private fun String.alias(indent: Int, pluginType: PluginType): String = buildString {
append(" ".repeat(indent))
append(this@alias)
append("[").append(substringAfterLast(":")).append("]:::")
append(pluginType.ref)
}
private fun Dependency.link(indent: Int) = buildString {
append(" ".repeat(indent))
append(project).append(" ")
append(
when (configuration) {
"api" -> "-->"
"implementation" -> "-.->"
else -> "-.->|$configuration|"
},
)
append(" ").append(dependency)
}
private fun PluginType.classDef() = "classDef $ref $style;"
}
@CacheableTask
private abstract class GraphUpdateTask : DefaultTask() {
@get:Input
abstract val projectPath: Property<String>
@get:InputFile
@get:PathSensitive(NONE)
abstract val input: RegularFileProperty
@get:InputFile
@get:PathSensitive(NONE)
abstract val legend: RegularFileProperty
@get:OutputFile
abstract val output: RegularFileProperty
override fun getDescription() = "Updates Markdown file with the corresponding dependency graph."
@TaskAction
operator fun invoke() = with(output.get().asFile) {
if (!exists()) {
createNewFile()
writeText(
"""
# `${projectPath.get()}`
## Module dependency graph
<!--region graph--> <!--endregion-->
""".trimIndent(),
)
}
val mermaid = input.get().asFile.readText().trimTrailingNewLines()
val legend = legend.get().asFile.readText().trimTrailingNewLines()
val regex = """(<!--region graph-->)(.*?)(<!--endregion-->)""".toRegex(DOT_MATCHES_ALL)
val text = readText().replace(regex) { match ->
val (start, _, end) = match.destructured
"""
|$start
|```mermaid
|$mermaid
|```
|
|<details><summary>📋 Graph legend</summary>
|
|```mermaid
|$legend
|```
|
|</details>
|$end
""".trimMargin()
}
writeText(text)
}
private fun String.trimTrailingNewLines() = lines()
.dropLastWhile(String::isBlank)
.joinToString(System.lineSeparator())
}

@ -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
@ -39,7 +38,7 @@ internal fun Project.configureKotlinAndroid(
compileSdk = 35
defaultConfig {
minSdk = 21
minSdk = 23
}
compileOptions {
@ -78,14 +77,16 @@ internal fun Project.configureKotlinJvm() {
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
// 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",

@ -14,28 +14,6 @@
* limitations under the License.
*/
buildscript {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
// This is used only for internal Google builds.
maven { url = uri("../nowinandroid-prebuilts/m2repository") }
}
dependencies {
classpath(libs.google.oss.licenses.plugin) {
exclude(group = "com.google.protobuf")
}
}
}
/*
* By listing all the plugins used throughout all subprojects in the root project build script, it
* ensures that the build script classpath remains the same for all projects. This avoids potential
@ -58,7 +36,7 @@ plugins {
alias(libs.plugins.hilt) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.secrets) apply false
alias(libs.plugins.google.osslicenses) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation
alias(libs.plugins.nowinandroid.root)
}

@ -1,3 +1,48 @@
# :core:analytics module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_analytics.svg)
# `:core:analytics`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
end
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,48 @@
# :core:common module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_common.svg)
# `:core:common`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:common[common]:::jvm-library
end
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,73 @@
# :core:data-test module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_data_test.svg)
# `:core:data-test`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:data-test[data-test]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
end
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data --> :core:database
:core:data --> :core:datastore
:core:data --> :core:network
:core:data -.-> :core:notifications
:core:data-test --> :core:data
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:network --> :core:common
:core:network --> :core:model
:core:notifications -.-> :core:common
:core:notifications --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,71 @@
# :core:data module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_data.svg)
# `:core:data`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
end
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data --> :core:database
:core:data --> :core:datastore
:core:data --> :core:network
:core:data -.-> :core:notifications
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:network --> :core:common
:core:network --> :core:model
:core:notifications -.-> :core:common
:core:notifications --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -22,11 +22,7 @@ plugins {
android {
namespace = "com.google.samples.apps.nowinandroid.core.data"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
testOptions.unitTests.isIncludeAndroidResources = true
}
dependencies {

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

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

@ -1,3 +1,51 @@
# :core:database module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_database.svg)
# `:core:database`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:database[database]:::android-library
:core:model[model]:::jvm-library
end
:core:database --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

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

@ -1,3 +1,48 @@
# :core:datastore-proto module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_proto.svg)
# `:core:datastore-proto`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:datastore-proto[datastore-proto]:::android-library
end
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,58 @@
# :core:datastore-test module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_test.svg)
# `:core:datastore-test`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:common[common]:::jvm-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:datastore-test[datastore-test]:::android-library
:core:model[model]:::jvm-library
end
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:datastore-test -.-> :core:common
:core:datastore-test -.-> :core:datastore
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,55 @@
# :core:datastore module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore.svg)
# `:core:datastore`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:common[common]:::jvm-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:model[model]:::jvm-library
end
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,48 @@
# :core:designsystem module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_designsystem.svg)
# `:core:designsystem`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:designsystem[designsystem]:::android-library
end
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -22,6 +22,7 @@ plugins {
android {
namespace = "com.google.samples.apps.nowinandroid.core.designsystem"
testOptions.unitTests.isIncludeAndroidResources = true
}
dependencies {

@ -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 = {},
) {

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,6 +22,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.LineHeightStyle.Alignment
import androidx.compose.ui.text.style.LineHeightStyle.Trim
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.sp
/**
@ -33,30 +35,40 @@ internal val NiaTypography = Typography(
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
displayMedium = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 45.sp,
lineHeight = 52.sp,
letterSpacing = 0.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
displaySmall = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 36.sp,
lineHeight = 44.sp,
letterSpacing = 0.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
headlineLarge = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
headlineMedium = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp,
letterSpacing = 0.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
headlineSmall = TextStyle(
fontWeight = FontWeight.Normal,
@ -67,6 +79,8 @@ internal val NiaTypography = Typography(
alignment = Alignment.Bottom,
trim = Trim.None,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
titleLarge = TextStyle(
fontWeight = FontWeight.Bold,
@ -77,18 +91,24 @@ internal val NiaTypography = Typography(
alignment = Alignment.Bottom,
trim = Trim.LastLineBottom,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
titleMedium = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
lineHeight = 24.sp,
letterSpacing = 0.1.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
titleSmall = TextStyle(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
// Default text style
bodyLarge = TextStyle(
@ -100,18 +120,24 @@ internal val NiaTypography = Typography(
alignment = Alignment.Center,
trim = Trim.None,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
bodyMedium = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
bodySmall = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.4.sp,
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
// Used for Button
labelLarge = TextStyle(
@ -123,6 +149,8 @@ internal val NiaTypography = Typography(
alignment = Alignment.Center,
trim = Trim.LastLineBottom,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
// Used for Navigation items
labelMedium = TextStyle(
@ -134,6 +162,8 @@ internal val NiaTypography = Typography(
alignment = Alignment.Center,
trim = Trim.LastLineBottom,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
// Used for Tag
labelSmall = TextStyle(
@ -145,5 +175,7 @@ internal val NiaTypography = Typography(
alignment = Alignment.Center,
trim = Trim.LastLineBottom,
),
textDirection = TextDirection.Ltr,
textAlign = TextAlign.Left,
),
)

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

@ -1,3 +1,74 @@
# :core:domain module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_domain.svg)
# `:core:domain`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:analytics[analytics]:::android-library
:core:common[common]:::jvm-library
:core:data[data]:::android-library
:core:database[database]:::android-library
:core:datastore[datastore]:::android-library
:core:datastore-proto[datastore-proto]:::android-library
:core:domain[domain]:::android-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
:core:notifications[notifications]:::android-library
end
:core:data -.-> :core:analytics
:core:data --> :core:common
:core:data --> :core:database
:core:data --> :core:datastore
:core:data --> :core:network
:core:data -.-> :core:notifications
:core:database --> :core:model
:core:datastore -.-> :core:common
:core:datastore --> :core:datastore-proto
:core:datastore --> :core:model
:core:domain --> :core:data
:core:domain --> :core:model
:core:network --> :core:common
:core:network --> :core:model
:core:notifications -.-> :core:common
:core:notifications --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,48 @@
# :core:model module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_model.svg)
# `:core:model`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:model[model]:::jvm-library
end
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -1,3 +1,53 @@
# :core:network module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_network.svg)
# `:core:network`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:common[common]:::jvm-library
:core:model[model]:::jvm-library
:core:network[network]:::android-library
end
:core:network --> :core:common
:core:network --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

@ -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 {
@ -27,15 +30,7 @@ android {
buildConfig = true
}
namespace = "com.google.samples.apps.nowinandroid.core.network"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
secrets {
defaultPropertiesFileName = "secrets.defaults.properties"
testOptions.unitTests.isIncludeAndroidResources = true
}
dependencies {
@ -52,3 +47,19 @@ 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))
properties["BACKEND_URL"]
}.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
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<String> = listOf(),
val topics: List<String> = emptyList(),
)

@ -1,3 +1,53 @@
# :core:notifications module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_notifications.svg)
# `:core:notifications`
## Module dependency graph
<!--region graph-->
```mermaid
---
config:
layout: elk
elk:
nodePlacementStrategy: SIMPLE
---
graph TB
subgraph :core
direction TB
:core:common[common]:::jvm-library
:core:model[model]:::jvm-library
:core:notifications[notifications]:::android-library
end
:core:notifications -.-> :core:common
:core:notifications --> :core:model
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
<details><summary>📋 Graph legend</summary>
```mermaid
graph TB
application[application]:::android-application
feature[feature]:::android-feature
library[library]:::android-library
jvm[jvm]:::jvm-library
application -.-> feature
library --> jvm
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```
</details>
<!--endregion-->

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save