@ -0,0 +1,25 @@
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
registries: "*"
|
||||
labels: [ "version update" ]
|
||||
groups:
|
||||
kotlin-ksp:
|
||||
patterns:
|
||||
- "org.jetbrains.kotlin:*"
|
||||
- "org.jetbrains.kotlin.jvm"
|
||||
- "com.google.devtools.ksp"
|
||||
open-pull-requests-limit: 10
|
||||
registries:
|
||||
maven-google:
|
||||
type: "maven-repository"
|
||||
url: "https://maven.google.com"
|
||||
replaces-base: true
|
@ -1,17 +1,25 @@
|
||||
Thanks for submitting a pull request. Please include the following information.
|
||||
**DO NOT CREATE A PULL REQUEST WITHOUT READING THESE INSTRUCTIONS**
|
||||
|
||||
**What I have done and why**
|
||||
Include a summary of what your pull request contains, and why you have made these changes.
|
||||
## Instructions
|
||||
Thanks for submitting a pull request. To accept your pull request we need you do a few things:
|
||||
|
||||
**If this is your first pull request**
|
||||
|
||||
- [Sign the contributors license agreement](https://cla.developers.google.com/)
|
||||
|
||||
**Ensure tests pass and code is formatted correctly**
|
||||
|
||||
Fixes #<issue_number_goes_here>
|
||||
- Run local tests on the `DemoDebug` variant by running `./gradlew testDemoDebug`
|
||||
- Fix code formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`
|
||||
|
||||
**Do tests pass?**
|
||||
- [ ] Run local tests on `DemoDebug` variant: `./gradlew testDemoDebug`
|
||||
- [ ] Check formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`
|
||||
**Add a description**
|
||||
|
||||
**Is this your first pull request?**
|
||||
- [ ] [Sign the CLA](https://cla.developers.google.com/)
|
||||
- [ ] Run `./tools/setup.sh`
|
||||
- [ ] Import the code formatting style as explained in [the setup script](/tools/setup.sh#L40).
|
||||
We need to know what you've done and why you've done it. Include a summary of what your pull request contains, and why you have made these changes. Include links to any relevant issues which it fixes.
|
||||
|
||||
[Here's an example](https://github.com/android/nowinandroid/pull/1257).
|
||||
|
||||
**NOW DELETE THIS LINE AND EVERYTHING ABOVE IT**
|
||||
|
||||
**What I have done and why**
|
||||
|
||||
\<add your PR description here\>
|
||||
|
@ -1,16 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base", "group:all", ":dependencyDashboard", "schedule:daily"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["org.objenesis:objenesis"],
|
||||
"allowedVersions": "<=2.6"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["com.google.protobuf"],
|
||||
"allowedVersions": "<=0.8.19"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
# :app-nia-catalog module
|
||||
|
||||
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.png)
|
||||
## Dependency graph
|
||||
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.svg)
|
||||
|
@ -0,0 +1,122 @@
|
||||
androidx.activity:activity-compose:1.8.2
|
||||
androidx.activity:activity-ktx:1.8.2
|
||||
androidx.activity:activity:1.8.2
|
||||
androidx.annotation:annotation-experimental:1.4.0
|
||||
androidx.annotation:annotation-jvm:1.8.0
|
||||
androidx.annotation:annotation:1.8.0
|
||||
androidx.appcompat:appcompat-resources:1.6.1
|
||||
androidx.arch.core:core-common:2.2.0
|
||||
androidx.arch.core:core-runtime:2.2.0
|
||||
androidx.autofill:autofill:1.0.0
|
||||
androidx.browser:browser:1.8.0
|
||||
androidx.collection:collection-jvm:1.4.0
|
||||
androidx.collection:collection-ktx:1.4.0
|
||||
androidx.collection:collection:1.4.0
|
||||
androidx.compose.animation:animation-android:1.7.0-beta01
|
||||
androidx.compose.animation:animation-core-android:1.7.0-beta01
|
||||
androidx.compose.animation:animation-core:1.7.0-beta01
|
||||
androidx.compose.animation:animation:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-android:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-layout-android:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-layout:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation:1.7.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-android:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive:1.0.0-beta01
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0-beta01
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite:1.3.0-beta01
|
||||
androidx.compose.material3:material3-android:1.3.0-beta01
|
||||
androidx.compose.material3:material3:1.3.0-beta01
|
||||
androidx.compose.material:material-icons-core-android:1.6.3
|
||||
androidx.compose.material:material-icons-core:1.6.3
|
||||
androidx.compose.material:material-icons-extended-android:1.6.3
|
||||
androidx.compose.material:material-icons-extended:1.6.3
|
||||
androidx.compose.material:material-ripple-android:1.7.0-beta01
|
||||
androidx.compose.material:material-ripple:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-android:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-saveable-android:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-saveable:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime:1.7.0-beta01
|
||||
androidx.compose.ui:ui-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-geometry-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-geometry:1.7.0-beta01
|
||||
androidx.compose.ui:ui-graphics-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-graphics:1.7.0-beta01
|
||||
androidx.compose.ui:ui-text-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-text:1.7.0-beta01
|
||||
androidx.compose.ui:ui-tooling-preview-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-tooling-preview:1.7.0-beta01
|
||||
androidx.compose.ui:ui-unit-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-unit:1.7.0-beta01
|
||||
androidx.compose.ui:ui-util-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-util:1.7.0-beta01
|
||||
androidx.compose.ui:ui:1.7.0-beta01
|
||||
androidx.compose:compose-bom:2024.02.02
|
||||
androidx.concurrent:concurrent-futures:1.1.0
|
||||
androidx.core:core-ktx:1.13.1
|
||||
androidx.core:core:1.13.1
|
||||
androidx.customview:customview-poolingcontainer:1.0.0
|
||||
androidx.customview:customview:1.0.0
|
||||
androidx.emoji2:emoji2:1.3.0
|
||||
androidx.exifinterface:exifinterface:1.3.7
|
||||
androidx.fragment:fragment:1.5.1
|
||||
androidx.graphics:graphics-path:1.0.1
|
||||
androidx.interpolator:interpolator:1.0.0
|
||||
androidx.lifecycle:lifecycle-common-java8:2.8.0
|
||||
androidx.lifecycle:lifecycle-common-jvm:2.8.0
|
||||
androidx.lifecycle:lifecycle-common:2.8.0
|
||||
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.0
|
||||
androidx.lifecycle:lifecycle-livedata-core:2.8.0
|
||||
androidx.lifecycle:lifecycle-livedata:2.8.0
|
||||
androidx.lifecycle:lifecycle-process:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime-android:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime-compose:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime-ktx:2.8.0
|
||||
androidx.lifecycle:lifecycle-runtime:2.8.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-android:2.8.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0
|
||||
androidx.lifecycle:lifecycle-viewmodel:2.8.0
|
||||
androidx.loader:loader:1.0.0
|
||||
androidx.metrics:metrics-performance:1.0.0-alpha04
|
||||
androidx.profileinstaller:profileinstaller:1.3.1
|
||||
androidx.savedstate:savedstate-ktx:1.2.1
|
||||
androidx.savedstate:savedstate:1.2.1
|
||||
androidx.startup:startup-runtime:1.1.1
|
||||
androidx.tracing:tracing-ktx:1.3.0-alpha02
|
||||
androidx.tracing:tracing:1.3.0-alpha02
|
||||
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-beta02
|
||||
androidx.window:window-core:1.3.0-beta02
|
||||
androidx.window:window:1.3.0-beta02
|
||||
com.google.accompanist:accompanist-drawablepainter:0.32.0
|
||||
com.google.code.findbugs:jsr305:3.0.2
|
||||
com.google.dagger:dagger-lint-aar:2.51.1
|
||||
com.google.dagger:dagger:2.51.1
|
||||
com.google.dagger:hilt-android:2.51.1
|
||||
com.google.dagger:hilt-core:2.51.1
|
||||
com.google.guava:listenablefuture:1.0
|
||||
com.squareup.okhttp3:okhttp:4.12.0
|
||||
com.squareup.okio:okio-jvm:3.8.0
|
||||
com.squareup.okio:okio:3.8.0
|
||||
io.coil-kt:coil-base:2.6.0
|
||||
io.coil-kt:coil-compose-base:2.6.0
|
||||
io.coil-kt:coil-compose:2.6.0
|
||||
io.coil-kt:coil:2.6.0
|
||||
javax.inject:javax.inject:1
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:2.0.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.0.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
|
||||
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0
|
||||
org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
|
||||
org.jetbrains:annotations:23.0.0
|
@ -1,3 +1,3 @@
|
||||
# :app module
|
||||
|
||||
![Dependency graph](../docs/images/graphs/dep_graph_app.png)
|
||||
## Dependency graph
|
||||
![Dependency graph](../docs/images/graphs/dep_graph_app.svg)
|
||||
|
@ -0,0 +1,226 @@
|
||||
androidx.activity:activity-compose:1.8.2
|
||||
androidx.activity:activity-ktx:1.8.2
|
||||
androidx.activity:activity:1.8.2
|
||||
androidx.annotation:annotation-experimental:1.4.0
|
||||
androidx.annotation:annotation-jvm:1.8.0
|
||||
androidx.annotation:annotation:1.8.0
|
||||
androidx.appcompat:appcompat-resources:1.7.0
|
||||
androidx.appcompat:appcompat:1.7.0
|
||||
androidx.arch.core:core-common:2.2.0
|
||||
androidx.arch.core:core-runtime:2.2.0
|
||||
androidx.autofill:autofill:1.0.0
|
||||
androidx.browser:browser:1.8.0
|
||||
androidx.collection:collection-jvm:1.4.0
|
||||
androidx.collection:collection-ktx:1.4.0
|
||||
androidx.collection:collection:1.4.0
|
||||
androidx.compose.animation:animation-android:1.7.0-beta01
|
||||
androidx.compose.animation:animation-core-android:1.7.0-beta01
|
||||
androidx.compose.animation:animation-core:1.7.0-beta01
|
||||
androidx.compose.animation:animation:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-android:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-layout-android:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation-layout:1.7.0-beta01
|
||||
androidx.compose.foundation:foundation:1.7.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-android:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-layout:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-beta01
|
||||
androidx.compose.material3.adaptive:adaptive:1.0.0-beta01
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.3.0-beta01
|
||||
androidx.compose.material3:material3-adaptive-navigation-suite:1.3.0-beta01
|
||||
androidx.compose.material3:material3-android:1.3.0-beta01
|
||||
androidx.compose.material3:material3-window-size-class-android:1.3.0-beta01
|
||||
androidx.compose.material3:material3-window-size-class:1.3.0-beta01
|
||||
androidx.compose.material3:material3:1.3.0-beta01
|
||||
androidx.compose.material:material-icons-core-android:1.6.3
|
||||
androidx.compose.material:material-icons-core:1.6.3
|
||||
androidx.compose.material:material-icons-extended-android:1.6.3
|
||||
androidx.compose.material:material-icons-extended:1.6.3
|
||||
androidx.compose.material:material-ripple-android:1.7.0-beta01
|
||||
androidx.compose.material:material-ripple:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-android:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-saveable-android:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-saveable:1.7.0-beta01
|
||||
androidx.compose.runtime:runtime-tracing:1.0.0-beta01
|
||||
androidx.compose.runtime:runtime:1.7.0-beta01
|
||||
androidx.compose.ui:ui-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-geometry-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-geometry:1.7.0-beta01
|
||||
androidx.compose.ui:ui-graphics-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-graphics:1.7.0-beta01
|
||||
androidx.compose.ui:ui-text-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-text:1.7.0-beta01
|
||||
androidx.compose.ui:ui-tooling-preview-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-tooling-preview:1.7.0-beta01
|
||||
androidx.compose.ui:ui-unit-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-unit:1.7.0-beta01
|
||||
androidx.compose.ui:ui-util-android:1.7.0-beta01
|
||||
androidx.compose.ui:ui-util:1.7.0-beta01
|
||||
androidx.compose.ui:ui:1.7.0-beta01
|
||||
androidx.compose:compose-bom:2024.02.02
|
||||
androidx.concurrent:concurrent-futures:1.1.0
|
||||
androidx.core:core-ktx:1.13.1
|
||||
androidx.core:core-splashscreen:1.0.1
|
||||
androidx.core:core:1.13.1
|
||||
androidx.cursoradapter:cursoradapter:1.0.0
|
||||
androidx.customview:customview-poolingcontainer:1.0.0
|
||||
androidx.customview:customview:1.0.0
|
||||
androidx.datastore:datastore-core:1.0.0
|
||||
androidx.datastore:datastore-preferences-core:1.0.0
|
||||
androidx.datastore:datastore-preferences:1.0.0
|
||||
androidx.datastore:datastore:1.0.0
|
||||
androidx.documentfile:documentfile:1.0.0
|
||||
androidx.drawerlayout:drawerlayout:1.0.0
|
||||
androidx.emoji2:emoji2-views-helper:1.3.0
|
||||
androidx.emoji2:emoji2:1.3.0
|
||||
androidx.exifinterface:exifinterface:1.3.7
|
||||
androidx.fragment:fragment:1.5.4
|
||||
androidx.graphics:graphics-path:1.0.1
|
||||
androidx.hilt:hilt-common:1.1.0
|
||||
androidx.hilt:hilt-navigation-compose:1.2.0
|
||||
androidx.hilt:hilt-navigation:1.2.0
|
||||
androidx.hilt:hilt-work:1.1.0
|
||||
androidx.interpolator:interpolator:1.0.0
|
||||
androidx.legacy:legacy-support-core-utils:1.0.0
|
||||
androidx.lifecycle:lifecycle-common-java8:2.8.1
|
||||
androidx.lifecycle:lifecycle-common-jvm:2.8.1
|
||||
androidx.lifecycle:lifecycle-common:2.8.1
|
||||
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.1
|
||||
androidx.lifecycle:lifecycle-livedata-core:2.8.1
|
||||
androidx.lifecycle:lifecycle-livedata:2.8.1
|
||||
androidx.lifecycle:lifecycle-process:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime-android:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime-compose:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime-ktx:2.8.1
|
||||
androidx.lifecycle:lifecycle-runtime:2.8.1
|
||||
androidx.lifecycle:lifecycle-service:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel-android:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.1
|
||||
androidx.lifecycle:lifecycle-viewmodel:2.8.1
|
||||
androidx.loader:loader:1.0.0
|
||||
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
|
||||
androidx.metrics:metrics-performance:1.0.0-alpha04
|
||||
androidx.navigation:navigation-common-ktx:2.8.0-alpha06
|
||||
androidx.navigation:navigation-common:2.8.0-alpha06
|
||||
androidx.navigation:navigation-compose:2.8.0-alpha06
|
||||
androidx.navigation:navigation-runtime-ktx:2.8.0-alpha06
|
||||
androidx.navigation:navigation-runtime:2.8.0-alpha06
|
||||
androidx.print:print:1.0.0
|
||||
androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
|
||||
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05
|
||||
androidx.profileinstaller:profileinstaller:1.3.1
|
||||
androidx.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.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.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-beta02
|
||||
androidx.window:window-core:1.3.0-beta02
|
||||
androidx.window:window:1.3.0-beta02
|
||||
androidx.work:work-runtime-ktx:2.9.0
|
||||
androidx.work:work-runtime:2.9.0
|
||||
com.caverock:androidsvg-aar:1.4
|
||||
com.google.accompanist:accompanist-drawablepainter:0.32.0
|
||||
com.google.accompanist:accompanist-permissions:0.34.0
|
||||
com.google.android.datatransport:transport-api:3.0.0
|
||||
com.google.android.datatransport:transport-backend-cct:3.1.9
|
||||
com.google.android.datatransport:transport-runtime:3.1.9
|
||||
com.google.android.gms:play-services-ads-identifier:18.0.0
|
||||
com.google.android.gms:play-services-base:18.0.1
|
||||
com.google.android.gms:play-services-basement:18.1.0
|
||||
com.google.android.gms:play-services-cloud-messaging:17.0.1
|
||||
com.google.android.gms:play-services-measurement-api:21.4.0
|
||||
com.google.android.gms:play-services-measurement-base:21.4.0
|
||||
com.google.android.gms:play-services-measurement-impl:21.4.0
|
||||
com.google.android.gms:play-services-measurement-sdk-api:21.4.0
|
||||
com.google.android.gms:play-services-measurement-sdk:21.4.0
|
||||
com.google.android.gms:play-services-measurement:21.4.0
|
||||
com.google.android.gms:play-services-oss-licenses:17.0.1
|
||||
com.google.android.gms:play-services-stats:17.0.2
|
||||
com.google.android.gms:play-services-tasks:18.0.2
|
||||
com.google.code.findbugs:jsr305:3.0.2
|
||||
com.google.dagger:dagger-lint-aar:2.51.1
|
||||
com.google.dagger:dagger:2.51.1
|
||||
com.google.dagger:hilt-android:2.51.1
|
||||
com.google.dagger:hilt-core:2.51.1
|
||||
com.google.errorprone:error_prone_annotations:2.11.0
|
||||
com.google.firebase:firebase-abt:21.1.1
|
||||
com.google.firebase:firebase-analytics-ktx:21.4.0
|
||||
com.google.firebase:firebase-analytics:21.4.0
|
||||
com.google.firebase:firebase-annotations:16.2.0
|
||||
com.google.firebase:firebase-bom:32.4.0
|
||||
com.google.firebase:firebase-common-ktx:20.4.2
|
||||
com.google.firebase:firebase-common:20.4.2
|
||||
com.google.firebase:firebase-components:17.1.5
|
||||
com.google.firebase:firebase-config:21.5.0
|
||||
com.google.firebase:firebase-crashlytics-ktx:18.5.0
|
||||
com.google.firebase:firebase-crashlytics:18.5.0
|
||||
com.google.firebase:firebase-datatransport:18.1.8
|
||||
com.google.firebase:firebase-encoders-json:18.0.1
|
||||
com.google.firebase:firebase-encoders-proto:16.0.0
|
||||
com.google.firebase:firebase-encoders:17.0.0
|
||||
com.google.firebase:firebase-iid-interop:17.1.0
|
||||
com.google.firebase:firebase-installations-interop:17.1.1
|
||||
com.google.firebase:firebase-installations:17.2.0
|
||||
com.google.firebase:firebase-measurement-connector:19.0.0
|
||||
com.google.firebase:firebase-messaging-ktx:23.3.0
|
||||
com.google.firebase:firebase-messaging:23.3.0
|
||||
com.google.firebase:firebase-perf-ktx:20.5.0
|
||||
com.google.firebase:firebase-perf:20.5.0
|
||||
com.google.firebase:firebase-sessions:1.1.0
|
||||
com.google.firebase:protolite-well-known-types:18.0.0
|
||||
com.google.guava:failureaccess:1.0.1
|
||||
com.google.guava:guava:31.1-android
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
|
||||
com.google.j2objc:j2objc-annotations:1.3
|
||||
com.google.protobuf:protobuf-javalite:4.26.1
|
||||
com.google.protobuf:protobuf-kotlin-lite:4.26.1
|
||||
com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0
|
||||
com.squareup.okhttp3:logging-interceptor:4.12.0
|
||||
com.squareup.okhttp3:okhttp:4.12.0
|
||||
com.squareup.okio:okio-jvm:3.8.0
|
||||
com.squareup.okio:okio:3.8.0
|
||||
com.squareup.retrofit2:retrofit:2.9.0
|
||||
io.coil-kt:coil-base:2.6.0
|
||||
io.coil-kt:coil-compose-base:2.6.0
|
||||
io.coil-kt:coil-compose:2.6.0
|
||||
io.coil-kt:coil-svg:2.6.0
|
||||
io.coil-kt:coil:2.6.0
|
||||
javax.inject:javax.inject:1
|
||||
org.checkerframework:checker-qual:3.12.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:2.0.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.0.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.0
|
||||
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0
|
||||
org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.3
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
|
||||
org.jetbrains:annotations:23.0.0
|
@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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.ui
|
||||
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
|
||||
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.testharness.TestHarness
|
||||
import com.google.samples.apps.nowinandroid.core.data.repository.CompositeUserNewsResourceRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
|
||||
import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepository
|
||||
import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository
|
||||
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
|
||||
import dagger.hilt.android.testing.BindValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Tests that the navigation UI is rendered correctly on different screen sizes.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
|
||||
@HiltAndroidTest
|
||||
class NavigationUiTest {
|
||||
|
||||
/**
|
||||
* Manages the components' state and is used to perform injection on your test
|
||||
*/
|
||||
@get:Rule(order = 0)
|
||||
val hiltRule = HiltAndroidRule(this)
|
||||
|
||||
/**
|
||||
* Create a temporary folder used to create a Data Store file. This guarantees that
|
||||
* the file is removed in between each test, preventing a crash.
|
||||
*/
|
||||
@BindValue
|
||||
@get:Rule(order = 1)
|
||||
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
|
||||
|
||||
/**
|
||||
* Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission.
|
||||
*/
|
||||
@get:Rule(order = 2)
|
||||
val postNotificationsPermission = GrantPostNotificationsPermissionRule()
|
||||
|
||||
/**
|
||||
* Use a test activity to set the content on.
|
||||
*/
|
||||
@get:Rule(order = 3)
|
||||
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
|
||||
|
||||
val userNewsResourceRepository = CompositeUserNewsResourceRepository(
|
||||
newsRepository = TestNewsRepository(),
|
||||
userDataRepository = TestUserDataRepository(),
|
||||
)
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
hiltRule.inject()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compactWidth_compactHeight_showsNavigationBar() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(400.dp, 400.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mediumWidth_compactHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(610.dp, 400.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expandedWidth_compactHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(900.dp, 400.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compactWidth_mediumHeight_showsNavigationBar() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(400.dp, 500.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mediumWidth_mediumHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(610.dp, 500.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expandedWidth_mediumHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(900.dp, 500.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun compactWidth_expandedHeight_showsNavigationBar() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(400.dp, 1000.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mediumWidth_expandedHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(610.dp, 1000.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expandedWidth_expandedHeight_showsNavigationRail() {
|
||||
composeTestRule.setContent {
|
||||
TestHarness(size = DpSize(900.dp, 1000.dp)) {
|
||||
BoxWithConstraints {
|
||||
NiaApp(
|
||||
windowSizeClass = WindowSizeClass.calculateFromSize(
|
||||
DpSize(maxWidth, maxHeight),
|
||||
),
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("NiaNavRail").assertIsDisplayed()
|
||||
composeTestRule.onNodeWithTag("NiaBottomBar").assertDoesNotExist()
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.ui.interests2pane
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class Interests2PaneViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
val selectedTopicId: StateFlow<String?> =
|
||||
savedStateHandle.getStateFlow(TOPIC_ID_ARG, savedStateHandle[TOPIC_ID_ARG])
|
||||
|
||||
fun onTopicClick(topicId: String?) {
|
||||
savedStateHandle[TOPIC_ID_ARG] = topicId
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.ui.interests2pane
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
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.ThreePaneScaffoldDestinationItem
|
||||
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
||||
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.setValue
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navArgument
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG
|
||||
import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
|
||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE
|
||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.createTopicRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
|
||||
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
|
||||
import java.util.UUID
|
||||
|
||||
private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route"
|
||||
|
||||
fun NavGraphBuilder.interestsListDetailScreen() {
|
||||
composable(
|
||||
route = INTERESTS_ROUTE,
|
||||
arguments = listOf(
|
||||
navArgument(TOPIC_ID_ARG) {
|
||||
type = NavType.StringType
|
||||
defaultValue = null
|
||||
nullable = true
|
||||
},
|
||||
),
|
||||
) {
|
||||
InterestsListDetailScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun InterestsListDetailScreen(
|
||||
viewModel: Interests2PaneViewModel = hiltViewModel(),
|
||||
) {
|
||||
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()
|
||||
InterestsListDetailScreen(
|
||||
selectedTopicId = selectedTopicId,
|
||||
onTopicClick = viewModel::onTopicClick,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
internal fun InterestsListDetailScreen(
|
||||
selectedTopicId: String?,
|
||||
onTopicClick: (String) -> Unit,
|
||||
) {
|
||||
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator(
|
||||
initialDestinationHistory = listOfNotNull(
|
||||
ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List),
|
||||
ThreePaneScaffoldDestinationItem<Nothing>(ListDetailPaneScaffoldRole.Detail).takeIf {
|
||||
selectedTopicId != null
|
||||
},
|
||||
),
|
||||
)
|
||||
BackHandler(listDetailNavigator.canNavigateBack()) {
|
||||
listDetailNavigator.navigateBack()
|
||||
}
|
||||
|
||||
var nestedNavHostStartDestination by remember {
|
||||
mutableStateOf(selectedTopicId?.let(::createTopicRoute) ?: TOPIC_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(DETAIL_PANE_NAVHOST_ROUTE)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, recreate the NavHost entirely, and start at the new destination
|
||||
nestedNavHostStartDestination = createTopicRoute(topicId)
|
||||
nestedNavKey = UUID.randomUUID()
|
||||
}
|
||||
listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
|
||||
}
|
||||
|
||||
ListDetailPaneScaffold(
|
||||
value = listDetailNavigator.scaffoldValue,
|
||||
directive = listDetailNavigator.scaffoldDirective,
|
||||
listPane = {
|
||||
AnimatedPane {
|
||||
InterestsRoute(
|
||||
onTopicClick = ::onTopicClickShowDetailPane,
|
||||
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
|
||||
)
|
||||
}
|
||||
},
|
||||
detailPane = {
|
||||
AnimatedPane {
|
||||
key(nestedNavKey) {
|
||||
NavHost(
|
||||
navController = nestedNavController,
|
||||
startDestination = nestedNavHostStartDestination,
|
||||
route = DETAIL_PANE_NAVHOST_ROUTE,
|
||||
) {
|
||||
topicScreen(
|
||||
showBackButton = !listDetailNavigator.isListPaneVisible(),
|
||||
onBackClick = listDetailNavigator::navigateBack,
|
||||
onTopicClick = ::onTopicClickShowDetailPane,
|
||||
)
|
||||
composable(route = TOPIC_ROUTE) {
|
||||
TopicDetailPlaceholder()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
private fun <T> ThreePaneScaffoldNavigator<T>.isListPaneVisible(): Boolean =
|
||||
scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
private fun <T> ThreePaneScaffoldNavigator<T>.isDetailPaneVisible(): Boolean =
|
||||
scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.util
|
||||
|
||||
import android.util.Log
|
||||
import androidx.profileinstaller.ProfileVerifier
|
||||
import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Logs the app's Baseline Profile Compilation Status using [ProfileVerifier].
|
||||
*
|
||||
* When delivering through Google Play, the baseline profile is compiled during installation.
|
||||
* In this case you will see the correct state logged without any further action necessary.
|
||||
* To verify baseline profile installation locally, you need to manually trigger baseline
|
||||
* profile installation.
|
||||
*
|
||||
* For immediate compilation, call:
|
||||
* ```bash
|
||||
* adb shell cmd package compile -f -m speed-profile com.example.macrobenchmark.target
|
||||
* ```
|
||||
* You can also trigger background optimizations:
|
||||
* ```bash
|
||||
* adb shell pm bg-dexopt-job
|
||||
* ```
|
||||
* Both jobs run asynchronously and might take some time complete.
|
||||
*
|
||||
* To see quick turnaround of the ProfileVerifier, we recommend using `speed-profile`.
|
||||
* If you don't do either of these steps, you might only see the profile status reported as
|
||||
* "enqueued for compilation" when running the sample locally.
|
||||
*
|
||||
* @see androidx.profileinstaller.ProfileVerifier.CompilationStatus.ResultCode
|
||||
*/
|
||||
class ProfileVerifierLogger @Inject constructor(
|
||||
@ApplicationScope private val scope: CoroutineScope,
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "ProfileInstaller"
|
||||
}
|
||||
|
||||
operator fun invoke() = scope.launch {
|
||||
val status = ProfileVerifier.getCompilationStatusAsync().await()
|
||||
Log.d(TAG, "Status code: ${status.profileInstallResultCode}")
|
||||
Log.d(
|
||||
TAG,
|
||||
when {
|
||||
status.isCompiledWithProfile -> "App compiled with profile"
|
||||
status.hasProfileEnqueuedForCompilation() -> "Profile enqueued for compilation"
|
||||
else -> "Profile not compiled nor enqueued"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.samples.apps.nowinandroid.ui
|
||||
|
||||
import android.view.WindowInsets
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.children
|
||||
|
||||
/**
|
||||
* A [DeviceConfigurationOverride] that allows overriding the [windowInsets] available
|
||||
* to the content under test.
|
||||
*/
|
||||
@Suppress("ktlint:standard:function-naming")
|
||||
fun DeviceConfigurationOverride.Companion.WindowInsets(
|
||||
windowInsets: WindowInsetsCompat,
|
||||
): DeviceConfigurationOverride = DeviceConfigurationOverride { contentUnderTest ->
|
||||
val currentContentUnderTest by rememberUpdatedState(contentUnderTest)
|
||||
val currentWindowInsets by rememberUpdatedState(windowInsets)
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
object : FrameLayout(context) {
|
||||
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
|
||||
children.forEach {
|
||||
it.dispatchApplyWindowInsets(currentWindowInsets.toWindowInsets())
|
||||
}
|
||||
return WindowInsetsCompat.CONSUMED.toWindowInsets()!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, but intercept the `requestApplyInsets` call via the deprecated
|
||||
* method.
|
||||
*/
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun requestFitSystemWindows() {
|
||||
dispatchApplyWindowInsets(currentWindowInsets.toWindowInsets()!!)
|
||||
}
|
||||
}.apply {
|
||||
addView(
|
||||
ComposeView(context).apply {
|
||||
setContent {
|
||||
currentContentUnderTest()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright 2023 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.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import androidx.compose.foundation.layout.windowInsetsEndWidth
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.layout.windowInsetsStartWidth
|
||||
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||
import androidx.compose.material3.SnackbarDuration.Indefinite
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.Posture
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toAndroidRect
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||
import androidx.compose.ui.test.ForcedSize
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpRect
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.roundToIntRect
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
|
||||
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
|
||||
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
|
||||
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
|
||||
import dagger.hilt.android.testing.BindValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import java.util.TimeZone
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Tests that the Snackbar is correctly displayed on different screen sizes.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
|
||||
// This allows enough room to render the content under test without clipping or scaling.
|
||||
@Config(application = HiltTestApplication::class, qualifiers = "w1000dp-h1000dp-480dpi")
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
@HiltAndroidTest
|
||||
class SnackbarInsetsScreenshotTests {
|
||||
|
||||
/**
|
||||
* Manages the components' state and is used to perform injection on your test
|
||||
*/
|
||||
@get:Rule(order = 0)
|
||||
val hiltRule = HiltAndroidRule(this)
|
||||
|
||||
/**
|
||||
* Create a temporary folder used to create a Data Store file. This guarantees that
|
||||
* the file is removed in between each test, preventing a crash.
|
||||
*/
|
||||
@BindValue
|
||||
@get:Rule(order = 1)
|
||||
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
|
||||
|
||||
/**
|
||||
* Use a test activity to set the content on.
|
||||
*/
|
||||
@get:Rule(order = 2)
|
||||
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var timeZoneMonitor: TimeZoneMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var userDataRepository: FakeUserDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var topicsRepository: TopicsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var userNewsResourceRepository: UserNewsResourceRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
hiltRule.inject()
|
||||
|
||||
// Configure user data
|
||||
runBlocking {
|
||||
userDataRepository.setShouldHideOnboarding(true)
|
||||
|
||||
userDataRepository.setFollowedTopicIds(
|
||||
setOf(topicsRepository.getTopics().first().first().id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setTimeZone() {
|
||||
// Make time zone deterministic in tests
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phone_noSnackbar() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
400.dp,
|
||||
500.dp,
|
||||
"insets_snackbar_compact_medium_noSnackbar",
|
||||
action = { },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_phone() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
400.dp,
|
||||
500.dp,
|
||||
"insets_snackbar_compact_medium",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_foldable() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
600.dp,
|
||||
600.dp,
|
||||
"insets_snackbar_medium_medium",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_tablet() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
900.dp,
|
||||
900.dp,
|
||||
"insets_snackbar_expanded_expanded",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
private fun testSnackbarScreenshotWithSize(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
width: Dp,
|
||||
height: Dp,
|
||||
screenshotName: String,
|
||||
action: suspend () -> Unit,
|
||||
) {
|
||||
lateinit var scope: CoroutineScope
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(
|
||||
// Replaces images with placeholders
|
||||
LocalInspectionMode provides true,
|
||||
) {
|
||||
scope = rememberCoroutineScope()
|
||||
|
||||
DeviceConfigurationOverride(
|
||||
DeviceConfigurationOverride.ForcedSize(DpSize(width, height)),
|
||||
) {
|
||||
DeviceConfigurationOverride(
|
||||
DeviceConfigurationOverride.WindowInsets(
|
||||
WindowInsetsCompat.Builder()
|
||||
.setInsets(
|
||||
WindowInsetsCompat.Type.statusBars(),
|
||||
DpRect(
|
||||
left = 0.dp,
|
||||
top = 64.dp,
|
||||
right = 0.dp,
|
||||
bottom = 0.dp,
|
||||
).toInsets(),
|
||||
)
|
||||
.setInsets(
|
||||
WindowInsetsCompat.Type.navigationBars(),
|
||||
DpRect(
|
||||
left = 64.dp,
|
||||
top = 0.dp,
|
||||
right = 64.dp,
|
||||
bottom = 64.dp,
|
||||
).toInsets(),
|
||||
)
|
||||
.build(),
|
||||
),
|
||||
) {
|
||||
BoxWithConstraints(Modifier.testTag("root")) {
|
||||
NiaTheme {
|
||||
val appState = rememberNiaAppState(
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
timeZoneMonitor = timeZoneMonitor,
|
||||
)
|
||||
NiaApp(
|
||||
appState = appState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
showSettingsDialog = false,
|
||||
onSettingsDismissed = {},
|
||||
onTopAppBarActionClick = {},
|
||||
windowAdaptiveInfo = WindowAdaptiveInfo(
|
||||
windowSizeClass = WindowSizeClass.compute(
|
||||
maxWidth.value,
|
||||
maxHeight.value,
|
||||
),
|
||||
windowPosture = Posture(),
|
||||
),
|
||||
)
|
||||
DebugVisibleWindowInsets()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
action()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithTag("root")
|
||||
.captureRoboImage(
|
||||
"src/testDemo/screenshots/$screenshotName.png",
|
||||
roborazziOptions = DefaultRoborazziOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DebugVisibleWindowInsets(
|
||||
modifier: Modifier = Modifier,
|
||||
debugColor: Color = Color.Magenta.copy(alpha = 0.5f),
|
||||
) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterStart)
|
||||
.fillMaxHeight()
|
||||
.windowInsetsStartWidth(WindowInsets.safeDrawing)
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Vertical))
|
||||
.background(debugColor),
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.fillMaxHeight()
|
||||
.windowInsetsEndWidth(WindowInsets.safeDrawing)
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Vertical))
|
||||
.background(debugColor),
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.fillMaxWidth()
|
||||
.windowInsetsTopHeight(WindowInsets.safeDrawing)
|
||||
.background(debugColor),
|
||||
)
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.windowInsetsBottomHeight(WindowInsets.safeDrawing)
|
||||
.background(debugColor),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DpRect.toInsets() = toInsets(LocalDensity.current)
|
||||
|
||||
private fun DpRect.toInsets(density: Density) =
|
||||
Insets.of(with(density) { toRect() }.roundToIntRect().toAndroidRect())
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2023 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.ui
|
||||
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.material3.SnackbarDuration.Indefinite
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.Posture
|
||||
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.test.DeviceConfigurationOverride
|
||||
import androidx.compose.ui.test.ForcedSize
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
|
||||
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
|
||||
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
|
||||
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
|
||||
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
|
||||
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
|
||||
import dagger.hilt.android.testing.BindValue
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import java.util.TimeZone
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Tests that the Snackbar is correctly displayed on different screen sizes.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
|
||||
// This allows enough room to render the content under test without clipping or scaling.
|
||||
@Config(application = HiltTestApplication::class, qualifiers = "w1000dp-h1000dp-480dpi")
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
@HiltAndroidTest
|
||||
class SnackbarScreenshotTests {
|
||||
|
||||
/**
|
||||
* Manages the components' state and is used to perform injection on your test
|
||||
*/
|
||||
@get:Rule(order = 0)
|
||||
val hiltRule = HiltAndroidRule(this)
|
||||
|
||||
/**
|
||||
* Create a temporary folder used to create a Data Store file. This guarantees that
|
||||
* the file is removed in between each test, preventing a crash.
|
||||
*/
|
||||
@BindValue
|
||||
@get:Rule(order = 1)
|
||||
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
|
||||
|
||||
/**
|
||||
* Use a test activity to set the content on.
|
||||
*/
|
||||
@get:Rule(order = 2)
|
||||
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
|
||||
|
||||
@Inject
|
||||
lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var timeZoneMonitor: TimeZoneMonitor
|
||||
|
||||
@Inject
|
||||
lateinit var userDataRepository: FakeUserDataRepository
|
||||
|
||||
@Inject
|
||||
lateinit var topicsRepository: TopicsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var userNewsResourceRepository: UserNewsResourceRepository
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
hiltRule.inject()
|
||||
|
||||
// Configure user data
|
||||
runBlocking {
|
||||
userDataRepository.setShouldHideOnboarding(true)
|
||||
|
||||
userDataRepository.setFollowedTopicIds(
|
||||
setOf(topicsRepository.getTopics().first().first().id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setTimeZone() {
|
||||
// Make time zone deterministic in tests
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun phone_noSnackbar() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
400.dp,
|
||||
500.dp,
|
||||
"snackbar_compact_medium_noSnackbar",
|
||||
action = { },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_phone() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
400.dp,
|
||||
500.dp,
|
||||
"snackbar_compact_medium",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_foldable() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
600.dp,
|
||||
600.dp,
|
||||
"snackbar_medium_medium",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snackbarShown_tablet() {
|
||||
val snackbarHostState = SnackbarHostState()
|
||||
testSnackbarScreenshotWithSize(
|
||||
snackbarHostState,
|
||||
900.dp,
|
||||
900.dp,
|
||||
"snackbar_expanded_expanded",
|
||||
) {
|
||||
snackbarHostState.showSnackbar(
|
||||
"This is a test snackbar message",
|
||||
actionLabel = "Action Label",
|
||||
duration = Indefinite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
private fun testSnackbarScreenshotWithSize(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
width: Dp,
|
||||
height: Dp,
|
||||
screenshotName: String,
|
||||
action: suspend () -> Unit,
|
||||
) {
|
||||
lateinit var scope: CoroutineScope
|
||||
composeTestRule.setContent {
|
||||
CompositionLocalProvider(
|
||||
// Replaces images with placeholders
|
||||
LocalInspectionMode provides true,
|
||||
) {
|
||||
scope = rememberCoroutineScope()
|
||||
|
||||
DeviceConfigurationOverride(
|
||||
DeviceConfigurationOverride.ForcedSize(DpSize(width, height)),
|
||||
) {
|
||||
BoxWithConstraints {
|
||||
NiaTheme {
|
||||
val appState = rememberNiaAppState(
|
||||
networkMonitor = networkMonitor,
|
||||
userNewsResourceRepository = userNewsResourceRepository,
|
||||
timeZoneMonitor = timeZoneMonitor,
|
||||
)
|
||||
NiaApp(
|
||||
appState = appState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
showSettingsDialog = false,
|
||||
onSettingsDismissed = {},
|
||||
onTopAppBarActionClick = {},
|
||||
windowAdaptiveInfo = WindowAdaptiveInfo(
|
||||
windowSizeClass = WindowSizeClass.compute(
|
||||
maxWidth.value,
|
||||
maxHeight.value,
|
||||
),
|
||||
windowPosture = Posture(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
action()
|
||||
}
|
||||
|
||||
composeTestRule.onRoot()
|
||||
.captureRoboImage(
|
||||
"src/testDemo/screenshots/$screenshotName.png",
|
||||
roborazziOptions = DefaultRoborazziOptions,
|
||||
)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 96 KiB |
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 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.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
|
||||
import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen
|
||||
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Baseline Profile of the "Bookmarks" screen
|
||||
*/
|
||||
class BookmarksBaselineProfile {
|
||||
@get:Rule val baselineProfileRule = BaselineProfileRule()
|
||||
|
||||
@Test
|
||||
fun generate() =
|
||||
baselineProfileRule.collect(PACKAGE_NAME) {
|
||||
startActivityAndAllowNotifications()
|
||||
|
||||
// Navigate to saved screen
|
||||
goToBookmarksScreen()
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 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.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
|
||||
import com.google.samples.apps.nowinandroid.interests.goToInterestsScreen
|
||||
import com.google.samples.apps.nowinandroid.interests.interestsScrollTopicsDownUp
|
||||
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Baseline Profile of the "Interests" screen
|
||||
*/
|
||||
class InterestsBaselineProfile {
|
||||
@get:Rule val baselineProfileRule = BaselineProfileRule()
|
||||
|
||||
@Test
|
||||
fun generate() =
|
||||
baselineProfileRule.collect(PACKAGE_NAME) {
|
||||
startActivityAndAllowNotifications()
|
||||
|
||||
// Navigate to interests screen
|
||||
goToInterestsScreen()
|
||||
interestsScrollTopicsDownUp()
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2022 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.baselineprofile
|
||||
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import com.google.samples.apps.nowinandroid.PACKAGE_NAME
|
||||
import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Baseline Profile for app startup. This profile also enables using [Dex Layout Optimizations](https://developer.android.com/topic/performance/baselineprofiles/dex-layout-optimizations)
|
||||
* via the `includeInStartupProfile` parameter.
|
||||
*/
|
||||
class StartupBaselineProfile {
|
||||
@get:Rule val baselineProfileRule = BaselineProfileRule()
|
||||
|
||||
@Test
|
||||
fun generate() = baselineProfileRule.collect(
|
||||
PACKAGE_NAME,
|
||||
includeInStartupProfile = true,
|
||||
profileBlock = MacrobenchmarkScope::startActivityAndAllowNotifications,
|
||||
)
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// This file contains classes (with possible wildcards) that the Compose Compiler will treat as stable.
|
||||
// It allows us to define classes that our not part of our codebase without wrapping them in a stable class.
|
||||
// For more information, check https://developer.android.com/jetpack/compose/performance/stability/fix#configuration-file
|
||||
|
||||
// We always use immutable classes for our data model, to avoid running the Compose compiler
|
||||
// in the module we declare it to be stable here.
|
||||
com.google.samples.apps.nowinandroid.core.model.data.*
|
||||
|
||||
// Java standard library classes
|
||||
java.time.ZoneId
|
||||
java.time.ZoneOffset
|
@ -0,0 +1,3 @@
|
||||
# :core:analytics module
|
||||
## Dependency graph
|
||||
![Dependency graph](../../docs/images/graphs/dep_graph_core_analytics.svg)
|