diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ed5fa237d..265ff2ba5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -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 # +- 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** +\ diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 001140a87..6bee0ddfb 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -17,6 +17,7 @@ jobs: permissions: contents: write + pull-requests: write timeout-minutes: 60 @@ -25,7 +26,7 @@ jobs: uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v3 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties @@ -100,12 +101,13 @@ jobs: commit_message: "🤖 Updates screenshots" # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots. - - name: Run local tests + - name: Run local tests and create report if: always() run: ./gradlew testDemoDebug :lint:test # Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when # https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a # release build + - name: Build all build type and flavor permutations run: ./gradlew :app:assemble :benchmarks:assemble -x pixel6Api33ProdNonMinifiedReleaseAndroidTest @@ -119,13 +121,20 @@ jobs: name: APKs path: '**/build/outputs/apk/**/*.apk' - - name: Upload test results (XML) + - name: Upload JVM local results (XML) if: always() uses: actions/upload-artifact@v4 with: - name: test-results + name: local-test-results path: '**/build/test-results/test*UnitTest/**.xml' + - name: Upload screenshot results (PNG) + if: always() + uses: actions/upload-artifact@v4 + with: + name: screenshot-test-results + path: '**/build/outputs/roborazzi/*_compare.png' + - name: Check lint run: ./gradlew :app:lintProdRelease :app-nia-catalog:lintRelease :lint:lint @@ -180,10 +189,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 - - name: Build projects before running emulator - run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest - - - name: Run instrumentation tests + - name: Build projects and run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} @@ -193,9 +199,41 @@ jobs: heap-size: 600M script: ./gradlew connectedDemoDebugAndroidTest --daemon + - name: Run local tests (including Roborazzi) for the combined coverage report (only API 30) + if: matrix.api-level == 30 + # There is no need to verify Roborazzi tests to generate coverage. + run: ./gradlew testDemoDebugUnitTest -Proborazzi.test.verify=false # Add Prod if we ever add JVM tests for prod + + # Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod + - name: Generate coverage reports for Debug variants (only API 30) + if: matrix.api-level == 30 + run: ./gradlew createDemoDebugCombinedCoverageReport + - name: Upload test reports if: always() uses: actions/upload-artifact@v4 with: name: test-reports-${{ matrix.api-level }} path: '**/build/reports/androidTests' + + - name: Display local test coverage (only API 30) + if: matrix.api-level == 30 + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + with: + title: Combined test coverage report + min-coverage-overall: 40 + min-coverage-changed-files: 60 + paths: | + ${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload local coverage reports (XML + HTML) (only API 30) + if: matrix.api-level == 30 + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + if-no-files-found: error + compression-level: 1 + overwrite: false + path: '**/build/reports/jacoco/' diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index 7de3cb11e..2184a4c50 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/wrapper-validation-action@v3 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties diff --git a/app-nia-catalog/build.gradle.kts b/app-nia-catalog/build.gradle.kts index 0991ea0c5..94d55b81c 100644 --- a/app-nia-catalog/build.gradle.kts +++ b/app-nia-catalog/build.gradle.kts @@ -59,7 +59,7 @@ android { // 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. // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this. - signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.named("debug").get() } } } diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 8a4bff7ab..686e709ed 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -1,6 +1,6 @@ -androidx.activity:activity-compose:1.8.0 -androidx.activity:activity-ktx:1.8.0 -androidx.activity:activity:1.8.0 +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.7.1 androidx.annotation:annotation:1.7.1 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1eb28cd46..47b8af943 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,7 +21,6 @@ plugins { alias(libs.plugins.nowinandroid.android.application.flavors) alias(libs.plugins.nowinandroid.android.application.jacoco) alias(libs.plugins.nowinandroid.android.hilt) - id("jacoco") alias(libs.plugins.nowinandroid.android.application.firebase) id("com.google.android.gms.oss-licenses-plugin") alias(libs.plugins.baselineprofile) @@ -53,7 +52,7 @@ android { // 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. // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this. - signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.named("debug").get() // Ensure Baseline Profile is fresh for release builds. baselineProfile.automaticGenerationDuringBuild = true } @@ -101,6 +100,7 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.androidx.profileinstaller) implementation(libs.androidx.tracing.ktx) + implementation(libs.androidx.window.core) implementation(libs.kotlinx.coroutines.guava) implementation(libs.coil.kt) @@ -112,9 +112,8 @@ dependencies { kspTest(libs.hilt.compiler) testImplementation(projects.core.dataTest) - testImplementation(libs.accompanist.testharness) testImplementation(libs.hilt.android.testing) - testImplementation(libs.work.testing) + testImplementation(projects.sync.syncTest) testDemoImplementation(libs.robolectric) testDemoImplementation(libs.roborazzi) @@ -126,8 +125,7 @@ dependencies { androidTestImplementation(projects.core.datastoreTest) androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.navigation.testing) - androidTestImplementation(libs.accompanist.testharness) - androidTestImplementation(libs.bundles.androidx.compose.ui.test) + androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.hilt.android.testing) baselineProfile(projects.benchmarks) diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 85adaf6fc..6544acde6 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -1,9 +1,9 @@ -androidx.activity:activity-compose:1.8.0 -androidx.activity:activity-ktx:1.8.0 -androidx.activity:activity:1.8.0 +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.7.1 -androidx.annotation:annotation:1.7.1 +androidx.annotation:annotation-jvm:1.8.0-beta01 +androidx.annotation:annotation:1.8.0-beta01 androidx.appcompat:appcompat-resources:1.6.1 androidx.appcompat:appcompat:1.6.1 androidx.arch.core:core-common:2.2.0 @@ -13,20 +13,20 @@ 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.6.3 -androidx.compose.animation:animation-core-android:1.6.3 -androidx.compose.animation:animation-core:1.6.3 -androidx.compose.animation:animation:1.6.3 -androidx.compose.foundation:foundation-android:1.6.3 -androidx.compose.foundation:foundation-layout-android:1.6.3 -androidx.compose.foundation:foundation-layout:1.6.3 -androidx.compose.foundation:foundation:1.6.3 -androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha08 -androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha08 -androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha08 -androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0-alpha08 -androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha08 -androidx.compose.material3.adaptive:adaptive:1.0.0-alpha08 +androidx.compose.animation:animation-android:1.7.0-alpha06 +androidx.compose.animation:animation-core-android:1.7.0-alpha06 +androidx.compose.animation:animation-core:1.7.0-alpha06 +androidx.compose.animation:animation:1.7.0-alpha06 +androidx.compose.foundation:foundation-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout:1.7.0-alpha06 +androidx.compose.foundation:foundation:1.7.0-alpha06 +androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha10 +androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha10 +androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha10 +androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0-alpha10 +androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha10 +androidx.compose.material3.adaptive:adaptive:1.0.0-alpha10 androidx.compose.material3:material3-android:1.2.1 androidx.compose.material3:material3-window-size-class-android:1.2.1 androidx.compose.material3:material3-window-size-class:1.2.1 @@ -37,25 +37,25 @@ 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.6.3 androidx.compose.material:material-ripple:1.6.3 -androidx.compose.runtime:runtime-android:1.6.3 -androidx.compose.runtime:runtime-saveable-android:1.6.3 -androidx.compose.runtime:runtime-saveable:1.6.3 +androidx.compose.runtime:runtime-android:1.7.0-alpha06 +androidx.compose.runtime:runtime-saveable-android:1.7.0-alpha06 +androidx.compose.runtime:runtime-saveable:1.7.0-alpha06 androidx.compose.runtime:runtime-tracing:1.0.0-beta01 -androidx.compose.runtime:runtime:1.6.3 -androidx.compose.ui:ui-android:1.6.3 -androidx.compose.ui:ui-geometry-android:1.6.3 -androidx.compose.ui:ui-geometry:1.6.3 -androidx.compose.ui:ui-graphics-android:1.6.3 -androidx.compose.ui:ui-graphics:1.6.3 -androidx.compose.ui:ui-text-android:1.6.3 -androidx.compose.ui:ui-text:1.6.3 -androidx.compose.ui:ui-tooling-preview-android:1.6.3 -androidx.compose.ui:ui-tooling-preview:1.6.3 -androidx.compose.ui:ui-unit-android:1.6.3 -androidx.compose.ui:ui-unit:1.6.3 -androidx.compose.ui:ui-util-android:1.6.3 -androidx.compose.ui:ui-util:1.6.3 -androidx.compose.ui:ui:1.6.3 +androidx.compose.runtime:runtime:1.7.0-alpha06 +androidx.compose.ui:ui-android:1.7.0-alpha06 +androidx.compose.ui:ui-geometry-android:1.7.0-alpha06 +androidx.compose.ui:ui-geometry:1.7.0-alpha06 +androidx.compose.ui:ui-graphics-android:1.7.0-alpha06 +androidx.compose.ui:ui-graphics:1.7.0-alpha06 +androidx.compose.ui:ui-text-android:1.7.0-alpha06 +androidx.compose.ui:ui-text:1.7.0-alpha06 +androidx.compose.ui:ui-tooling-preview-android:1.7.0-alpha06 +androidx.compose.ui:ui-tooling-preview:1.7.0-alpha06 +androidx.compose.ui:ui-unit-android:1.7.0-alpha06 +androidx.compose.ui:ui-unit:1.7.0-alpha06 +androidx.compose.ui:ui-util-android:1.7.0-alpha06 +androidx.compose.ui:ui-util:1.7.0-alpha06 +androidx.compose.ui:ui:1.7.0-alpha06 androidx.compose:compose-bom:2024.02.02 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.12.0 @@ -74,34 +74,40 @@ 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.1 +androidx.graphics:graphics-path:1.0.0-beta02 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.7.0 -androidx.lifecycle:lifecycle-common:2.7.0 -androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0 -androidx.lifecycle:lifecycle-livedata-core:2.7.0 -androidx.lifecycle:lifecycle-livedata:2.7.0 -androidx.lifecycle:lifecycle-process:2.7.0 -androidx.lifecycle:lifecycle-runtime-compose:2.7.0 -androidx.lifecycle:lifecycle-runtime-ktx:2.7.0 -androidx.lifecycle:lifecycle-runtime:2.7.0 -androidx.lifecycle:lifecycle-service:2.7.0 -androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0 -androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0 -androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0 -androidx.lifecycle:lifecycle-viewmodel:2.7.0 +androidx.lifecycle:lifecycle-common-java8:2.8.0-alpha04 +androidx.lifecycle:lifecycle-common-jvm:2.8.0-alpha04 +androidx.lifecycle:lifecycle-common:2.8.0-alpha04 +androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.0-alpha04 +androidx.lifecycle:lifecycle-livedata-core:2.8.0-alpha04 +androidx.lifecycle:lifecycle-livedata:2.8.0-alpha04 +androidx.lifecycle:lifecycle-process:2.8.0-alpha04 +androidx.lifecycle:lifecycle-runtime-android:2.8.0-alpha04 +androidx.lifecycle:lifecycle-runtime-compose:2.8.0-alpha04 +androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.0-alpha04 +androidx.lifecycle:lifecycle-runtime-ktx:2.8.0-alpha04 +androidx.lifecycle:lifecycle-runtime:2.8.0-alpha04 +androidx.lifecycle:lifecycle-service:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel-android:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0-alpha04 +androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha04 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.7.4 -androidx.navigation:navigation-common:2.7.4 -androidx.navigation:navigation-compose:2.7.4 -androidx.navigation:navigation-runtime-ktx:2.7.4 -androidx.navigation:navigation-runtime:2.7.4 +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 @@ -123,9 +129,9 @@ 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-alpha02 -androidx.window:window-core:1.3.0-alpha02 -androidx.window:window:1.3.0-alpha02 +androidx.window:window-core-android:1.3.0-alpha03 +androidx.window:window-core:1.3.0-alpha03 +androidx.window:window:1.3.0-alpha03 androidx.work:work-runtime-ktx:2.9.0 androidx.work:work-runtime:2.9.0 com.caverock:androidsvg-aar:1.4 @@ -182,8 +188,8 @@ 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:3.25.2 -com.google.protobuf:protobuf-kotlin-lite:3.25.2 +com.google.protobuf:protobuf-javalite:4.26.0 +com.google.protobuf:protobuf-kotlin-lite:4.26.0 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 diff --git a/app/prodRelease-badging.txt b/app/prodRelease-badging.txt index 9ae76fff2..769e0a6e4 100644 --- a/app/prodRelease-badging.txt +++ b/app/prodRelease-badging.txt @@ -6,9 +6,9 @@ uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.POST_NOTIFICATIONS' uses-permission: name='android.permission.WAKE_LOCK' uses-permission: name='com.google.android.c2dm.permission.RECEIVE' -uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE' uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED' uses-permission: name='android.permission.FOREGROUND_SERVICE' +uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE' uses-permission: name='com.google.samples.apps.nowinandroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION' application-label:'Now in Android' application-label-af:'Now in Android' @@ -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' @@ -119,3 +119,4 @@ supports-screens: 'small' 'normal' 'large' 'xlarge' supports-any-density: 'true' locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'sk' 'sl' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'uk' 'ur' 'uz' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu' densities: '120' '160' '240' '320' '480' '640' '65534' +native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64' diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt index 5d2e12b5c..03cf653ae 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationUiTest.kt @@ -20,13 +20,14 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.ForcedSize import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.unit.Dp 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.data.util.TimeZoneMonitor @@ -95,7 +96,9 @@ class NavigationUiTest { @Test fun compactWidth_compactHeight_showsNavigationBar() { composeTestRule.setContent { - TestHarness(size = DpSize(400.dp, 400.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(400.dp, 400.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -109,7 +112,9 @@ class NavigationUiTest { @Test fun mediumWidth_compactHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(610.dp, 400.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(610.dp, 400.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -123,7 +128,9 @@ class NavigationUiTest { @Test fun expandedWidth_compactHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(900.dp, 400.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 400.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -137,7 +144,9 @@ class NavigationUiTest { @Test fun compactWidth_mediumHeight_showsNavigationBar() { composeTestRule.setContent { - TestHarness(size = DpSize(400.dp, 500.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(400.dp, 500.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -151,7 +160,9 @@ class NavigationUiTest { @Test fun mediumWidth_mediumHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(610.dp, 500.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(610.dp, 500.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -165,7 +176,9 @@ class NavigationUiTest { @Test fun expandedWidth_mediumHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(900.dp, 500.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 500.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -179,7 +192,9 @@ class NavigationUiTest { @Test fun compactWidth_expandedHeight_showsNavigationBar() { composeTestRule.setContent { - TestHarness(size = DpSize(400.dp, 1000.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(400.dp, 1000.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -193,7 +208,9 @@ class NavigationUiTest { @Test fun mediumWidth_expandedHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(610.dp, 1000.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(610.dp, 1000.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } @@ -207,7 +224,9 @@ class NavigationUiTest { @Test fun expandedWidth_expandedHeight_showsNavigationRail() { composeTestRule.setContent { - TestHarness(size = DpSize(900.dp, 1000.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.ForcedSize(DpSize(900.dp, 1000.dp)), + ) { BoxWithConstraints { NiaApp(fakeAppState(maxWidth, maxHeight)) } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index eaed15a4e..4c8232a26 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.ui +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets @@ -75,17 +76,13 @@ import com.google.samples.apps.nowinandroid.navigation.NiaNavHost import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR -@OptIn( - ExperimentalMaterial3Api::class, - ExperimentalComposeUiApi::class, -) @Composable -fun NiaApp(appState: NiaAppState) { +fun NiaApp(appState: NiaAppState, modifier: Modifier = Modifier) { val shouldShowGradientBackground = appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU var showSettingsDialog by rememberSaveable { mutableStateOf(false) } - NiaBackground { + NiaBackground(modifier = modifier) { NiaGradientBackground( gradientColors = if (shouldShowGradientBackground) { LocalGradientColors.current @@ -108,95 +105,125 @@ fun NiaApp(appState: NiaAppState) { } } - if (showSettingsDialog) { - SettingsDialog( - onDismiss = { showSettingsDialog = false }, + NiaApp( + appState = appState, + snackbarHostState = snackbarHostState, + showSettingsDialog = showSettingsDialog, + onSettingsDismissed = { showSettingsDialog = false }, + onTopAppBarActionClick = { showSettingsDialog = true }, + ) + } + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +internal fun NiaApp( + appState: NiaAppState, + snackbarHostState: SnackbarHostState, + showSettingsDialog: Boolean, + onSettingsDismissed: () -> Unit, + onTopAppBarActionClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val unreadDestinations by appState.topLevelDestinationsWithUnreadResources + .collectAsStateWithLifecycle() + + if (showSettingsDialog) { + SettingsDialog( + onDismiss = { onSettingsDismissed() }, + ) + } + Scaffold( + modifier = modifier.semantics { + testTagsAsResourceId = true + }, + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onBackground, + contentWindowInsets = WindowInsets(0, 0, 0, 0), + snackbarHost = { SnackbarHost(snackbarHostState) }, + bottomBar = { + if (appState.shouldShowBottomBar) { + NiaBottomBar( + destinations = appState.topLevelDestinations, + destinationsWithUnreadResources = unreadDestinations, + onNavigateToDestination = appState::navigateToTopLevelDestination, + currentDestination = appState.currentDestination, + modifier = Modifier.testTag("NiaBottomBar"), + ) + } + }, + ) { padding -> + Row( + Modifier + .fillMaxSize() + .padding(padding) + .consumeWindowInsets(padding) + .windowInsetsPadding( + WindowInsets.safeDrawing.only( + WindowInsetsSides.Horizontal, + ), + ), + ) { + if (appState.shouldShowNavRail) { + NiaNavRail( + destinations = appState.topLevelDestinations, + destinationsWithUnreadResources = unreadDestinations, + onNavigateToDestination = appState::navigateToTopLevelDestination, + currentDestination = appState.currentDestination, + modifier = Modifier + .testTag("NiaNavRail") + .safeDrawingPadding(), ) } - val unreadDestinations by appState.topLevelDestinationsWithUnreadResources.collectAsStateWithLifecycle() - - Scaffold( - modifier = Modifier.semantics { - testTagsAsResourceId = true - }, - containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onBackground, - contentWindowInsets = WindowInsets(0, 0, 0, 0), - snackbarHost = { SnackbarHost(snackbarHostState) }, - bottomBar = { - if (appState.shouldShowBottomBar) { - NiaBottomBar( - destinations = appState.topLevelDestinations, - destinationsWithUnreadResources = unreadDestinations, - onNavigateToDestination = appState::navigateToTopLevelDestination, - currentDestination = appState.currentDestination, - modifier = Modifier.testTag("NiaBottomBar"), - ) - } - }, - ) { padding -> - Row( - Modifier - .fillMaxSize() - .padding(padding) - .consumeWindowInsets(padding) - .windowInsetsPadding( - WindowInsets.safeDrawing.only( - WindowInsetsSides.Horizontal, - ), + Column(Modifier.fillMaxSize()) { + // Show the top app bar on top level destinations. + val destination = appState.currentTopLevelDestination + val shouldShowTopAppBar = destination != null + if (destination != null) { + NiaTopAppBar( + titleRes = destination.titleTextId, + navigationIcon = NiaIcons.Search, + navigationIconContentDescription = stringResource( + id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description, ), - ) { - if (appState.shouldShowNavRail) { - NiaNavRail( - destinations = appState.topLevelDestinations, - destinationsWithUnreadResources = unreadDestinations, - onNavigateToDestination = appState::navigateToTopLevelDestination, - currentDestination = appState.currentDestination, - modifier = Modifier - .testTag("NiaNavRail") - .safeDrawingPadding(), - ) - } - - Column(Modifier.fillMaxSize()) { - // Show the top app bar on top level destinations. - val destination = appState.currentTopLevelDestination - if (destination != null) { - NiaTopAppBar( - titleRes = destination.titleTextId, - navigationIcon = NiaIcons.Search, - navigationIconContentDescription = stringResource( - id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description, - ), - actionIcon = NiaIcons.Settings, - actionIconContentDescription = stringResource( - id = settingsR.string.feature_settings_top_app_bar_action_icon_description, - ), - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent, - ), - onActionClick = { showSettingsDialog = true }, - onNavigationClick = { appState.navigateToSearch() }, - ) - } + actionIcon = NiaIcons.Settings, + actionIconContentDescription = stringResource( + id = settingsR.string.feature_settings_top_app_bar_action_icon_description, + ), + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent, + ), + onActionClick = { onTopAppBarActionClick() }, + onNavigationClick = { appState.navigateToSearch() }, + ) + } - NiaNavHost( - appState = appState, - onShowSnackbar = { message, action -> - snackbarHostState.showSnackbar( - message = message, - actionLabel = action, - duration = Short, - ) == ActionPerformed - }, + Box( + modifier = if (shouldShowTopAppBar) { + Modifier.consumeWindowInsets( + WindowInsets.safeDrawing.only(WindowInsetsSides.Top), ) - } - - // TODO: We may want to add padding or spacer when the snackbar is shown so that - // content doesn't display behind it. + } else { + Modifier + }, + ) { + NiaNavHost( + appState = appState, + onShowSnackbar = { message, action -> + snackbarHostState.showSnackbar( + message = message, + actionLabel = action, + duration = Short, + ) == ActionPerformed + }, + ) } } + + // TODO: We may want to add padding or spacer when the snackbar is shown so that + // content doesn't display behind it. } } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt index d618c2d47..40ce9c116 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt @@ -27,7 +27,8 @@ import javax.inject.Inject class Interests2PaneViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { - val selectedTopicId: StateFlow = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null) + val selectedTopicId: StateFlow = + savedStateHandle.getStateFlow(TOPIC_ID_ARG, savedStateHandle[TOPIC_ID_ARG]) fun onTopicClick(topicId: String?) { savedStateHandle[TOPIC_ID_ARG] = topicId diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 335f83371..ada4e49d1 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -18,14 +18,21 @@ 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.LaunchedEffect 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 @@ -39,8 +46,10 @@ import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERES 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" @@ -76,17 +85,42 @@ internal fun InterestsListDetailScreen( selectedTopicId: String?, onTopicClick: (String) -> Unit, ) { - val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() + val listDetailNavigator = rememberListDetailPaneScaffoldNavigator( + initialDestinationHistory = listOfNotNull( + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List), + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail).takeIf { + selectedTopicId != null + }, + ), + ) BackHandler(listDetailNavigator.canNavigateBack()) { listDetailNavigator.navigateBack() } - val nestedNavController = rememberNavController() + 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) - nestedNavController.navigateToTopic(topicId) { - popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + 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) } @@ -95,34 +129,34 @@ internal fun InterestsListDetailScreen( value = listDetailNavigator.scaffoldValue, directive = listDetailNavigator.scaffoldDirective, listPane = { - InterestsRoute( - onTopicClick = ::onTopicClickShowDetailPane, - highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), - ) - }, - detailPane = { - NavHost( - navController = nestedNavController, - startDestination = TOPIC_ROUTE, - route = DETAIL_PANE_NAVHOST_ROUTE, - ) { - topicScreen( - showBackButton = !listDetailNavigator.isListPaneVisible(), - onBackClick = listDetailNavigator::navigateBack, + AnimatedPane { + InterestsRoute( onTopicClick = ::onTopicClickShowDetailPane, + highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), ) - composable(route = TOPIC_ROUTE) { - TopicDetailPlaceholder() + } + }, + 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() + } + } } } }, ) - LaunchedEffect(Unit) { - if (selectedTopicId != null) { - // Initial topic ID was provided when navigating to Interests, so show its details. - onTopicClickShowDetailPane(selectedTopicId) - } - } } @OptIn(ExperimentalMaterial3AdaptiveApi::class) diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index 83ca1bb3d..32bdfe9d9 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -16,23 +16,18 @@ package com.google.samples.apps.nowinandroid.ui -import android.util.Log -import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.CompositionLocalProvider 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.test.platform.app.InstrumentationRegistry -import androidx.work.Configuration -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository @@ -109,17 +104,6 @@ class NiaAppScreenSizesScreenshotTests { @Before fun setup() { - val config = Configuration.Builder() - .setMinimumLoggingLevel(Log.DEBUG) - .setExecutor(SynchronousExecutor()) - .build() - - // Initialize WorkManager for instrumentation tests. - WorkManagerTestInitHelper.initializeTestWorkManager( - InstrumentationRegistry.getInstrumentation().context, - config, - ) - hiltRule.inject() // Configure user data @@ -143,19 +127,19 @@ class NiaAppScreenSizesScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(size = DpSize(width, height)) { - BoxWithConstraints { - NiaTheme { - val fakeAppState = rememberNiaAppState( - windowSizeClass = WindowSizeClass.calculateFromSize( - DpSize(maxWidth, maxHeight), - ), - networkMonitor = networkMonitor, - userNewsResourceRepository = userNewsResourceRepository, - timeZoneMonitor = timeZoneMonitor, - ) - NiaApp(fakeAppState) - } + DeviceConfigurationOverride( + override = DeviceConfigurationOverride.ForcedSize(DpSize(width, height)), + ) { + NiaTheme { + val fakeAppState = rememberNiaAppState( + windowSizeClass = WindowSizeClass.calculateFromSize( + DpSize(width, height), + ), + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, + ) + NiaApp(fakeAppState) } } } diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt new file mode 100644 index 000000000..ab67f8399 --- /dev/null +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt @@ -0,0 +1,239 @@ +/* + * 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.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowSizeClass +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 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. + */ +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@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() + + @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, + ) + } + } + + 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 { + val appState = rememberNiaAppState( + windowSizeClass = WindowSizeClass.calculateFromSize( + DpSize(maxWidth, maxHeight), + ), + networkMonitor = networkMonitor, + userNewsResourceRepository = userNewsResourceRepository, + timeZoneMonitor = timeZoneMonitor, + ) + NiaTheme { + NiaApp(appState, snackbarHostState, false, {}, {}) + } + } + } + } + } + + scope.launch { + action() + } + + composeTestRule.onRoot() + .captureRoboImage( + "src/testDemo/screenshots/$screenshotName.png", + roborazziOptions = DefaultRoborazziOptions, + ) + } +} diff --git a/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png index 1c9213f3e..279efe6d0 100644 Binary files a/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png and b/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png differ diff --git a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png index 70af31fa7..53bf6f3c5 100644 Binary files a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png index 233718a57..3e38938d6 100644 Binary files a/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png and b/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png differ diff --git a/app/src/testDemo/screenshots/snackbar_compact_medium.png b/app/src/testDemo/screenshots/snackbar_compact_medium.png new file mode 100644 index 000000000..147c9ce6b Binary files /dev/null and b/app/src/testDemo/screenshots/snackbar_compact_medium.png differ diff --git a/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png b/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png new file mode 100644 index 000000000..2767ff9b5 Binary files /dev/null and b/app/src/testDemo/screenshots/snackbar_compact_medium_noSnackbar.png differ diff --git a/app/src/testDemo/screenshots/snackbar_expanded_expanded.png b/app/src/testDemo/screenshots/snackbar_expanded_expanded.png new file mode 100644 index 000000000..5360d68de Binary files /dev/null and b/app/src/testDemo/screenshots/snackbar_expanded_expanded.png differ diff --git a/app/src/testDemo/screenshots/snackbar_medium_medium.png b/app/src/testDemo/screenshots/snackbar_medium_medium.png new file mode 100644 index 000000000..b60112101 Binary files /dev/null and b/app/src/testDemo/screenshots/snackbar_medium_medium.png differ diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt index 4c3acc520..ac385b0d9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt @@ -15,6 +15,7 @@ */ import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin import org.gradle.api.Project @@ -23,13 +24,15 @@ import org.gradle.kotlin.dsl.getByType class AndroidApplicationJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("org.gradle.jacoco") - apply("com.android.application") + pluginManager.apply("jacoco") + val androidExtension = extensions.getByType() + + androidExtension.buildTypes.configureEach { + enableAndroidTestCoverage = true + enableUnitTestCoverage = true } - val extension = extensions.getByType() - configureJacoco(extension) + + configureJacoco(extensions.getByType()) } } - -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt index 86ca091c3..6f2ff60c5 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +import com.android.build.api.dsl.LibraryExtension +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin @@ -23,13 +25,15 @@ import org.gradle.kotlin.dsl.getByType class AndroidLibraryJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("org.gradle.jacoco") - apply("com.android.library") + pluginManager.apply("jacoco") + val androidExtension = extensions.getByType() + + androidExtension.buildTypes.configureEach { + enableAndroidTestCoverage = true + enableUnitTestCoverage = true } - val extension = extensions.getByType() - configureJacoco(extension) + + configureJacoco(extensions.getByType()) } } - -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index 29d31f9e6..dbca79a5e 100644 --- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -15,6 +15,7 @@ */ import androidx.room.gradle.RoomExtension +import com.google.devtools.ksp.gradle.KspExtension import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project @@ -28,6 +29,10 @@ class AndroidRoomConventionPlugin : Plugin { pluginManager.apply("androidx.room") pluginManager.apply("com.google.devtools.ksp") + extensions.configure { + arg("room.generateKotlin", "true") + } + extensions.configure { // The schemas directory contains a schema file for each version of the Room database. // This is required to enable Room auto migrations. diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt index 234313e1f..e38c5b300 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt @@ -57,6 +57,7 @@ internal fun Project.configureAndroidCompose( kotlinOptions { freeCompilerArgs += buildComposeMetricsParameters() freeCompilerArgs += stabilityConfiguration() + freeCompilerArgs += strongSkippingConfiguration() } } } @@ -84,6 +85,7 @@ private fun Project.buildComposeMetricsParameters(): List { "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath ) } + return metricParameters.toList() } @@ -91,3 +93,8 @@ private fun Project.stabilityConfiguration() = listOf( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=${project.rootDir.absolutePath}/compose_compiler_config.conf", ) + +private fun Project.strongSkippingConfiguration() = listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:experimentalStrongSkipping=true", +) diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt index 596c4f579..7820a978e 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * 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. @@ -16,8 +16,13 @@ package com.google.samples.apps.nowinandroid +import com.android.build.api.artifact.ScopedArtifact import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.ScopedArtifacts import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.register @@ -32,13 +37,24 @@ private val coverageExclusions = listOf( "**/R.class", "**/R\$*.class", "**/BuildConfig.*", - "**/Manifest*.*" + "**/Manifest*.*", + "**/*_Hilt*.class", + "**/Hilt_*.class", ) private fun String.capitalize() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } +/** + * Creates a new task that generates a combined coverage report with data from local and + * instrumented tests. + * + * `create{variant}CombinedCoverageReport` + * + * Note that coverage data must exist before running the task. This allows us to run device + * tests on CI using a different Github Action or an external device farm. + */ internal fun Project.configureJacoco( androidComponentsExtension: AndroidComponentsExtension<*, *, *>, ) { @@ -46,37 +62,53 @@ internal fun Project.configureJacoco( toolVersion = libs.findVersion("jacoco").get().toString() } - val jacocoTestReport = tasks.create("jacocoTestReport") - androidComponentsExtension.onVariants { variant -> - val testTaskName = "test${variant.name.capitalize()}UnitTest" + val myObjFactory = project.objects val buildDir = layout.buildDirectory.get().asFile - val reportTask = tasks.register("jacoco${testTaskName.capitalize()}Report", JacocoReport::class) { - dependsOn(testTaskName) + val allJars: ListProperty = myObjFactory.listProperty(RegularFile::class.java) + val allDirectories: ListProperty = myObjFactory.listProperty(Directory::class.java) + val reportTask = + tasks.register("create${variant.name.capitalize()}CombinedCoverageReport", JacocoReport::class) { - reports { - xml.required.set(true) - html.required.set(true) - } - - classDirectories.setFrom( - fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") { - exclude(coverageExclusions) + classDirectories.setFrom( + allJars, + allDirectories.map { dirs -> + dirs.map { dir -> + myObjFactory.fileTree().setDir(dir).exclude(coverageExclusions) + } + } + ) + reports { + xml.required.set(true) + html.required.set(true) } - ) - sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin")) - executionData.setFrom(file("$buildDir/jacoco/$testTaskName.exec")) - } + // TODO: This is missing files in src/debug/, src/prod, src/demo, src/demoDebug... + sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin")) + + executionData.setFrom( + project.fileTree("$buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest") + .matching { include("**/*.exec") }, + + project.fileTree("$buildDir/outputs/code_coverage/${variant.name}AndroidTest") + .matching { include("**/*.ec") } + ) + } + - jacocoTestReport.dependsOn(reportTask) + variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT) + .use(reportTask) + .toGet( + ScopedArtifact.CLASSES, + { _ -> allJars }, + { _ -> allDirectories }, + ) } tasks.withType().configureEach { configure { // Required for JaCoCo + Robolectric // https://github.com/robolectric/robolectric/issues/2230 - // TODO: Consider removing if not we don't add Robolectric isIncludeNoLocationClasses = true // Required for JDK 11 with the above diff --git a/compose_compiler_config.conf b/compose_compiler_config.conf index 2341256f4..d47946206 100644 --- a/compose_compiler_config.conf +++ b/compose_compiler_config.conf @@ -2,5 +2,10 @@ // 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 diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt index 070c7ed38..0cdec6090 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt @@ -39,7 +39,7 @@ import javax.inject.Inject * This allows us to run the app with fake data, without needing an internet connection or working * backend. */ -internal class FakeNewsRepository @Inject constructor( +class FakeNewsRepository @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, private val datasource: DemoNiaNetworkDataSource, ) : NewsRepository { diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt index 4871baad9..61ab422af 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt @@ -30,7 +30,7 @@ import javax.inject.Inject * This allows us to run the app with fake data, without needing an internet connection or working * backend. */ -internal class FakeUserDataRepository @Inject constructor( +class FakeUserDataRepository @Inject constructor( private val niaPreferencesDataSource: NiaPreferencesDataSource, ) : UserDataRepository { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt index e9599c555..b2a642cf9 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt @@ -26,57 +26,68 @@ import android.net.NetworkRequest.Builder import android.os.Build.VERSION import android.os.Build.VERSION_CODES import androidx.core.content.getSystemService +import androidx.tracing.trace +import com.google.samples.apps.nowinandroid.core.network.Dispatcher +import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject internal class ConnectivityManagerNetworkMonitor @Inject constructor( @ApplicationContext private val context: Context, + @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, ) : NetworkMonitor { override val isOnline: Flow = callbackFlow { - val connectivityManager = context.getSystemService() - if (connectivityManager == null) { - channel.trySend(false) - channel.close() - return@callbackFlow - } + trace("NetworkMonitor.callbackFlow") { + val connectivityManager = context.getSystemService() + if (connectivityManager == null) { + channel.trySend(false) + channel.close() + return@callbackFlow + } - /** - * The callback's methods are invoked on changes to *any* network matching the [NetworkRequest], - * not just the active network. So we can simply track the presence (or absence) of such [Network]. - */ - val callback = object : NetworkCallback() { + /** + * The callback's methods are invoked on changes to *any* network matching the [NetworkRequest], + * not just the active network. So we can simply track the presence (or absence) of such [Network]. + */ + val callback = object : NetworkCallback() { - private val networks = mutableSetOf() + private val networks = mutableSetOf() - override fun onAvailable(network: Network) { - networks += network - channel.trySend(true) - } + override fun onAvailable(network: Network) { + networks += network + channel.trySend(true) + } - override fun onLost(network: Network) { - networks -= network - channel.trySend(networks.isNotEmpty()) + override fun onLost(network: Network) { + networks -= network + channel.trySend(networks.isNotEmpty()) + } } - } - val request = Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build() - connectivityManager.registerNetworkCallback(request, callback) + trace("NetworkMonitor.registerNetworkCallback") { + val request = Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(request, callback) + } - /** - * Sends the latest connectivity status to the underlying channel. - */ - channel.trySend(connectivityManager.isCurrentlyConnected()) + /** + * Sends the latest connectivity status to the underlying channel. + */ + channel.trySend(connectivityManager.isCurrentlyConnected()) - awaitClose { - connectivityManager.unregisterNetworkCallback(callback) + awaitClose { + connectivityManager.unregisterNetworkCallback(callback) + } } } + .flowOn(ioDispatcher) .conflate() @Suppress("DEPRECATION") diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt similarity index 90% rename from core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt rename to core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt index afbbf42e6..e7456054e 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * 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. @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.database +package com.google.samples.apps.nowinandroid.core.database.di +import com.google.samples.apps.nowinandroid.core.database.NiaDatabase import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao import com.google.samples.apps.nowinandroid.core.database.dao.RecentSearchQueryDao diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt similarity index 86% rename from core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt rename to core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt index c6e33f284..d79d35948 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * 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. @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.database +package com.google.samples.apps.nowinandroid.core.database.di import android.content.Context import androidx.room.Room +import com.google.samples.apps.nowinandroid.core.database.NiaDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index a8d6ea9c5..3810080f0 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -40,7 +40,8 @@ dependencies { implementation(libs.coil.kt.compose) testImplementation(libs.androidx.compose.ui.test) - testImplementation(libs.accompanist.testharness) + testImplementation(libs.androidx.compose.ui.testManifest) + testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) testImplementation(projects.core.screenshotTesting) diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/icon/NiaIcons.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/icon/NiaIcons.kt index 6b77f7394..dfa68e772 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/icon/NiaIcons.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/icon/NiaIcons.kt @@ -23,7 +23,6 @@ import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.outlined.Bookmarks import androidx.compose.material.icons.outlined.Upcoming import androidx.compose.material.icons.rounded.Add -import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.Bookmark import androidx.compose.material.icons.rounded.BookmarkBorder import androidx.compose.material.icons.rounded.Bookmarks @@ -33,7 +32,6 @@ import androidx.compose.material.icons.rounded.Grid3x3 import androidx.compose.material.icons.rounded.Person import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Settings -import androidx.compose.material.icons.rounded.ShortText import androidx.compose.material.icons.rounded.Upcoming import androidx.compose.material.icons.rounded.ViewDay import androidx.compose.ui.graphics.vector.ImageVector diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/FilterChipScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/FilterChipScreenshotTests.kt index 92d5467ae..7a6a92a1d 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/FilterChipScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/FilterChipScreenshotTests.kt @@ -21,12 +21,15 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.FontScale +import androidx.compose.ui.test.ForcedSize import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.then import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilterChip import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme @@ -78,7 +81,10 @@ class FilterChipScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(fontScale = 2f, size = DpSize(80.dp, 40.dp)) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.FontScale(2f) then + DeviceConfigurationOverride.ForcedSize(DpSize(80.dp, 40.dp)), + ) { NiaTheme { NiaBackground { NiaFilterChip(selected = true, onSelectedChange = {}) { diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/NavigationScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/NavigationScreenshotTests.kt index 1d1e70f7d..be2c6fa28 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/NavigationScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/NavigationScreenshotTests.kt @@ -23,10 +23,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.FontScale import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onRoot import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBar import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBarItem import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons @@ -66,7 +67,9 @@ class NavigationScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(fontScale = 2f) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.FontScale(2f), + ) { NiaTheme { NiaNavigationBarExample("Looong item") } diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TabsScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TabsScreenshotTests.kt index 19f7bea87..8ab711505 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TabsScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TabsScreenshotTests.kt @@ -22,10 +22,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.FontScale import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onRoot import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTab import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTabRow import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme @@ -62,7 +63,9 @@ class TabsScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(fontScale = 2f) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.FontScale(2f), + ) { NiaTheme { NiaTabsExample("Looooong item") } diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt index d18f7dec5..8a519942d 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt @@ -20,10 +20,11 @@ import androidx.activity.ComponentActivity import androidx.compose.material3.Text import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.FontScale import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onRoot import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopicTag import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions @@ -61,7 +62,9 @@ class TagScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(fontScale = 2f) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.Companion.FontScale(2f), + ) { NiaTheme { NiaTopicTag(followed = true, onClick = {}) { Text("LOOOOONG TOPIC") diff --git a/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt similarity index 99% rename from core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt rename to core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt index 92e94f4e5..1044a5443 100644 --- a/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt @@ -44,6 +44,8 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.TintTheme import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner import kotlin.test.assertEquals /** @@ -54,6 +56,7 @@ import kotlin.test.assertEquals * [LocalBackgroundTheme] — have the expected values for a given theme mode, as specified by the * design system. */ +@RunWith(RobolectricTestRunner::class) class ThemeTest { @get:Rule diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TopAppBarScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TopAppBarScreenshotTests.kt index 325d9a501..5988ed592 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TopAppBarScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TopAppBarScreenshotTests.kt @@ -16,16 +16,16 @@ package com.google.samples.apps.nowinandroid.core.designsystem -import android.R.string import androidx.activity.ComponentActivity import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DeviceConfigurationOverride +import androidx.compose.ui.test.FontScale import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onRoot import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopAppBar import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme @@ -63,7 +63,9 @@ class TopAppBarScreenshotTests { CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(fontScale = 2f) { + DeviceConfigurationOverride( + DeviceConfigurationOverride.FontScale(2f), + ) { NiaTheme { NiaTopAppBarExample() } diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png index 6d023a207..5aa1eb89a 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_115.png differ diff --git a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png index c125faccf..74309056f 100644 Binary files a/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png and b/core/designsystem/src/test/screenshots/LoadingWheel/LoadingWheel_animation_20.png differ diff --git a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt deleted file mode 100644 index 3e3e1952e..000000000 --- a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.core.domain - -import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -/** - * A use case which returns total count of *Fts tables - */ -class GetSearchContentsCountUseCase @Inject constructor( - private val searchContentsRepository: SearchContentsRepository, -) { - operator fun invoke(): Flow = - searchContentsRepository.getSearchContentsCount() -} diff --git a/core/screenshot-testing/build.gradle.kts b/core/screenshot-testing/build.gradle.kts index dd6e43fb3..1b816fc67 100644 --- a/core/screenshot-testing/build.gradle.kts +++ b/core/screenshot-testing/build.gradle.kts @@ -26,7 +26,7 @@ android { dependencies { api(libs.bundles.androidx.compose.ui.test) api(libs.roborazzi) - implementation(libs.accompanist.testharness) + implementation(libs.androidx.compose.ui.test) implementation(libs.androidx.activity.compose) implementation(libs.robolectric) implementation(projects.core.designsystem) diff --git a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index 468fff8df..f11651220 100644 --- a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -25,6 +25,8 @@ import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.DarkMode +import androidx.compose.ui.test.DeviceConfigurationOverride import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.rules.ActivityScenarioRule @@ -32,7 +34,6 @@ import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions import com.github.takahirom.roborazzi.captureRoboImage -import com.google.accompanist.testharness.TestHarness import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import org.robolectric.RuntimeEnvironment @@ -75,7 +76,9 @@ fun AndroidComposeTestRule, A>.c CompositionLocalProvider( LocalInspectionMode provides true, ) { - TestHarness(darkMode = darkMode) { + DeviceConfigurationOverride( + override = DeviceConfigurationOverride.Companion.DarkMode(darkMode), + ) { body() } } diff --git a/docs/images/graphs/dep_graph_app.svg b/docs/images/graphs/dep_graph_app.svg index 57a592a8e..8a94310b2 100644 --- a/docs/images/graphs/dep_graph_app.svg +++ b/docs/images/graphs/dep_graph_app.svg @@ -1,463 +1,383 @@ - - - - - -G - - - -:app - -:app - - - -:feature:interests - -:feature:interests - - - -:app->:feature:interests - - - - - -:feature:foryou - -:feature:foryou - - - -:app->:feature:foryou - - - - - -:feature:bookmarks - -:feature:bookmarks - - - -:app->:feature:bookmarks - - - - - -:feature:topic - -:feature:topic - - - -:app->:feature:topic - - - - - -:feature:search - -:feature:search - - - -:app->:feature:search - - - - - -:feature:settings - -:feature:settings - - - -:app->:feature:settings - - - - - -:core:common - -:core:common - - - -:app->:core:common - - - - - -:core:ui - -:core:ui - - - -:app->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:app->:core:designsystem - - - - - -:core:data - -:core:data - - - -:app->:core:data - - - - - -:core:model - -:core:model - - - -:app->:core:model - - - - - -:core:analytics - -:core:analytics - - - -:app->:core:analytics - - - - - -:sync:work - -:sync:work - - - -:app->:sync:work - - - - - -:feature:interests->:core:ui - - - - - -:feature:interests->:core:designsystem - - - - - -:feature:interests->:core:data - - - - - -:core:domain - -:core:domain - - - -:feature:interests->:core:domain - - - - - -:feature:foryou->:core:ui - - - - - -:feature:foryou->:core:designsystem - - - - - -:feature:foryou->:core:data - - - - - -:feature:foryou->:core:domain - - - - - -:feature:bookmarks->:core:ui - - - - - -:feature:bookmarks->:core:designsystem - - - - - -:feature:bookmarks->:core:data - - - - - -:feature:topic->:core:ui - - - - - -:feature:topic->:core:designsystem - - - - - -:feature:topic->:core:data - - - - - -:feature:search->:core:ui - - - - - -:feature:search->:core:designsystem - - - - - -:feature:search->:core:data - - - - - -:feature:search->:core:domain - - - - - -:feature:settings->:core:ui - - - - - -:feature:settings->:core:designsystem - - - - - -:feature:settings->:core:data - - - - - -:core:ui->:core:designsystem - - - - - -:core:ui->:core:model - - - - - -:core:ui->:core:analytics - - - - - -:core:data->:core:common - - - - - -:core:data->:core:analytics - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:sync:work->:core:data - - - - - -:sync:work->:core:analytics - - - - - -:core:domain->:core:data - - - - - -:core:domain->:core:model - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - + + + + G + + + :app + + :app + + + :feature:interests + + :feature:interests + + + :app->:feature:interests + + + + + :feature:foryou + + :feature:foryou + + + :app->:feature:foryou + + + + + :feature:bookmarks + + :feature:bookmarks + + + :app->:feature:bookmarks + + + + + :feature:topic + + :feature:topic + + + :app->:feature:topic + + + + + :feature:search + + :feature:search + + + :app->:feature:search + + + + + :feature:settings + + :feature:settings + + + :app->:feature:settings + + + + + :core:common + + :core:common + + + :app->:core:common + + + + + :core:ui + + :core:ui + + + :app->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :app->:core:designsystem + + + + + :core:data + + :core:data + + + :app->:core:data + + + + + :core:model + + :core:model + + + :app->:core:model + + + + + :core:analytics + + :core:analytics + + + :app->:core:analytics + + + + + :sync:work + + :sync:work + + + :app->:sync:work + + + + + :feature:interests->:core:ui + + + + + :feature:interests->:core:designsystem + + + + + :feature:interests->:core:data + + + + + :core:domain + + :core:domain + + + :feature:interests->:core:domain + + + + + :feature:foryou->:core:ui + + + + + :feature:foryou->:core:designsystem + + + + + :feature:foryou->:core:data + + + + + :feature:foryou->:core:domain + + + + + :feature:bookmarks->:core:ui + + + + + :feature:bookmarks->:core:designsystem + + + + + :feature:bookmarks->:core:data + + + + + :feature:topic->:core:ui + + + + + :feature:topic->:core:designsystem + + + + + :feature:topic->:core:data + + + + + :feature:search->:core:ui + + + + + :feature:search->:core:designsystem + + + + + :feature:search->:core:data + + + + + :feature:search->:core:domain + + + + + :feature:settings->:core:ui + + + + + :feature:settings->:core:designsystem + + + + + :feature:settings->:core:data + + + + + :core:ui->:core:designsystem + + + + + :core:ui->:core:model + + + + + :core:ui->:core:analytics + + + + + :core:data->:core:common + + + + + :core:data->:core:analytics + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :sync:work->:core:data + + + + + :sync:work->:core:analytics + + + + + :core:domain->:core:data + + + + + :core:domain->:core:model + + + + + :core:database->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore->:core:model + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:common + + + + + :core:network->:core:model + + + + + :core:notifications->:core:common + + + + + :core:notifications->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_app_nia_catalog.svg b/docs/images/graphs/dep_graph_app_nia_catalog.svg index b58415cef..fa7cfca5a 100644 --- a/docs/images/graphs/dep_graph_app_nia_catalog.svg +++ b/docs/images/graphs/dep_graph_app_nia_catalog.svg @@ -1,73 +1,58 @@ - - - - - -G - - - -:app-nia-catalog - -:app-nia-catalog - - - -:core:designsystem - -:core:designsystem - - - -:app-nia-catalog->:core:designsystem - - - - - -:core:ui - -:core:ui - - - -:app-nia-catalog->:core:ui - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - + + + + G + + + :app-nia-catalog + + :app-nia-catalog + + + :core:designsystem + + :core:designsystem + + + :app-nia-catalog->:core:designsystem + + + + + :core:ui + + :core:ui + + + :app-nia-catalog->:core:ui + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_analytics.svg b/docs/images/graphs/dep_graph_core_analytics.svg index ac21c0707..9cb11e4eb 100644 --- a/docs/images/graphs/dep_graph_core_analytics.svg +++ b/docs/images/graphs/dep_graph_core_analytics.svg @@ -1,19 +1,13 @@ - - - - - -G - - - -:core:analytics - -:core:analytics - - + + + + G + + + :core:analytics + + :core:analytics + + diff --git a/docs/images/graphs/dep_graph_core_common.svg b/docs/images/graphs/dep_graph_core_common.svg index c91f33853..a1cdcb610 100644 --- a/docs/images/graphs/dep_graph_core_common.svg +++ b/docs/images/graphs/dep_graph_core_common.svg @@ -1,19 +1,13 @@ - - - - - -G - - - -:core:common - -:core:common - - + + + + G + + + :core:common + + :core:common + + diff --git a/docs/images/graphs/dep_graph_core_data.svg b/docs/images/graphs/dep_graph_core_data.svg index cacf03a1f..8637b06ee 100644 --- a/docs/images/graphs/dep_graph_core_data.svg +++ b/docs/images/graphs/dep_graph_core_data.svg @@ -1,151 +1,123 @@ - - - - - -G - - - -:core:data - -:core:data - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:analytics - -:core:analytics - - - -:core:data->:core:analytics - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:model - -:core:model - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - + + + + G + + + :core:data + + :core:data + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:analytics + + :core:analytics + + + :core:data->:core:analytics + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:model + + :core:model + + + :core:database->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore->:core:model + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:common + + + + + :core:network->:core:model + + + + + :core:notifications->:core:common + + + + + :core:notifications->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_data_test.svg b/docs/images/graphs/dep_graph_core_data_test.svg index 162c83f10..798696c11 100644 --- a/docs/images/graphs/dep_graph_core_data_test.svg +++ b/docs/images/graphs/dep_graph_core_data_test.svg @@ -1,163 +1,133 @@ - - - - - -G - - - -:core:data-test - -:core:data-test - - - -:core:data - -:core:data - - - -:core:data-test->:core:data - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:analytics - -:core:analytics - - - -:core:data->:core:analytics - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:model - -:core:model - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - + + + + G + + + :core:data-test + + :core:data-test + + + :core:data + + :core:data + + + :core:data-test->:core:data + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:analytics + + :core:analytics + + + :core:data->:core:analytics + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:model + + :core:model + + + :core:database->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore->:core:model + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:common + + + + + :core:network->:core:model + + + + + :core:notifications->:core:common + + + + + :core:notifications->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_database.svg b/docs/images/graphs/dep_graph_core_database.svg index 9e907b96f..dd3aef7ee 100644 --- a/docs/images/graphs/dep_graph_core_database.svg +++ b/docs/images/graphs/dep_graph_core_database.svg @@ -1,31 +1,23 @@ - - - - - -G - - - -:core:database - -:core:database - - - -:core:model - -:core:model - - - -:core:database->:core:model - - - - + + + + G + + + :core:database + + :core:database + + + :core:model + + :core:model + + + :core:database->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_datastore.svg b/docs/images/graphs/dep_graph_core_datastore.svg index cfcf78db2..3bf13d087 100644 --- a/docs/images/graphs/dep_graph_core_datastore.svg +++ b/docs/images/graphs/dep_graph_core_datastore.svg @@ -1,55 +1,43 @@ - - - - - -G - - - -:core:datastore - -:core:datastore - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:model - -:core:model - - - -:core:datastore->:core:model - - - - - -:core:common - -:core:common - - - -:core:datastore->:core:common - - - - + + + + G + + + :core:datastore + + :core:datastore + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:model + + :core:model + + + :core:datastore->:core:model + + + + + :core:common + + :core:common + + + :core:datastore->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_core_datastore_proto.svg b/docs/images/graphs/dep_graph_core_datastore_proto.svg index d572d0ea7..fd3bba9b6 100644 --- a/docs/images/graphs/dep_graph_core_datastore_proto.svg +++ b/docs/images/graphs/dep_graph_core_datastore_proto.svg @@ -1,19 +1,13 @@ - - - - - -G - - - -:core:datastore-proto - -:core:datastore-proto - - + + + + G + + + :core:datastore-proto + + :core:datastore-proto + + diff --git a/docs/images/graphs/dep_graph_core_datastore_test.svg b/docs/images/graphs/dep_graph_core_datastore_test.svg index ca8d3f84b..e42e5e795 100644 --- a/docs/images/graphs/dep_graph_core_datastore_test.svg +++ b/docs/images/graphs/dep_graph_core_datastore_test.svg @@ -1,73 +1,58 @@ - - - - - -G - - - -:core:datastore-test - -:core:datastore-test - - - -:core:common - -:core:common - - - -:core:datastore-test->:core:common - - - - - -:core:datastore - -:core:datastore - - - -:core:datastore-test->:core:datastore - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:model - -:core:model - - - -:core:datastore->:core:model - - - - + + + + G + + + :core:datastore-test + + :core:datastore-test + + + :core:common + + :core:common + + + :core:datastore-test->:core:common + + + + + :core:datastore + + :core:datastore + + + :core:datastore-test->:core:datastore + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:model + + :core:model + + + :core:datastore->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_designsystem.svg b/docs/images/graphs/dep_graph_core_designsystem.svg index f46f075f0..f33d70705 100644 --- a/docs/images/graphs/dep_graph_core_designsystem.svg +++ b/docs/images/graphs/dep_graph_core_designsystem.svg @@ -1,19 +1,13 @@ - - - - - -G - - - -:core:designsystem - -:core:designsystem - - + + + + G + + + :core:designsystem + + :core:designsystem + + diff --git a/docs/images/graphs/dep_graph_core_domain.svg b/docs/images/graphs/dep_graph_core_domain.svg index 1c97b64e8..68543466c 100644 --- a/docs/images/graphs/dep_graph_core_domain.svg +++ b/docs/images/graphs/dep_graph_core_domain.svg @@ -1,169 +1,138 @@ - - - - - -G - - - -:core:domain - -:core:domain - - - -:core:data - -:core:data - - - -:core:domain->:core:data - - - - - -:core:model - -:core:model - - - -:core:domain->:core:model - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:analytics - -:core:analytics - - - -:core:data->:core:analytics - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :core:domain + + :core:domain + + + :core:data + + :core:data + + + :core:domain->:core:data + + + + + :core:model + + :core:model + + + :core:domain->:core:model + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:analytics + + :core:analytics + + + :core:data->:core:analytics + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_core_model.svg b/docs/images/graphs/dep_graph_core_model.svg index 290457d6c..66959f2f1 100644 --- a/docs/images/graphs/dep_graph_core_model.svg +++ b/docs/images/graphs/dep_graph_core_model.svg @@ -1,19 +1,13 @@ - - - - - -G - - - -:core:model - -:core:model - - + + + + G + + + :core:model + + :core:model + + diff --git a/docs/images/graphs/dep_graph_core_network.svg b/docs/images/graphs/dep_graph_core_network.svg index ea804bcff..9902ee8e3 100644 --- a/docs/images/graphs/dep_graph_core_network.svg +++ b/docs/images/graphs/dep_graph_core_network.svg @@ -1,43 +1,33 @@ - - - - - -G - - - -:core:network - -:core:network - - - -:core:common - -:core:common - - - -:core:network->:core:common - - - - - -:core:model - -:core:model - - - -:core:network->:core:model - - - - + + + + G + + + :core:network + + :core:network + + + :core:common + + :core:common + + + :core:network->:core:common + + + + + :core:model + + :core:model + + + :core:network->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_core_notifications.svg b/docs/images/graphs/dep_graph_core_notifications.svg index cf25ca32e..3a1126ca8 100644 --- a/docs/images/graphs/dep_graph_core_notifications.svg +++ b/docs/images/graphs/dep_graph_core_notifications.svg @@ -1,43 +1,33 @@ - - - - - -G - - - -:core:notifications - -:core:notifications - - - -:core:model - -:core:model - - - -:core:notifications->:core:model - - - - - -:core:common - -:core:common - - - -:core:notifications->:core:common - - - - + + + + G + + + :core:notifications + + :core:notifications + + + :core:model + + :core:model + + + :core:notifications->:core:model + + + + + :core:common + + :core:common + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_core_screenshot_testing.svg b/docs/images/graphs/dep_graph_core_screenshot_testing.svg index 3c2a7b401..8021dcda6 100644 --- a/docs/images/graphs/dep_graph_core_screenshot_testing.svg +++ b/docs/images/graphs/dep_graph_core_screenshot_testing.svg @@ -1,31 +1,33 @@ - - - - - -G - - - -:core:screenshot-testing - -:core:screenshot-testing - - - -:core:designsystem - -:core:designsystem - - - -:core:screenshot-testing->:core:designsystem - - - - + + + + G + + + :core:screenshot-testing + + :core:screenshot-testing + + + :core:common + + :core:common + + + :core:screenshot-testing->:core:common + + + + + :core:designsystem + + :core:designsystem + + + :core:screenshot-testing->:core:designsystem + + + + diff --git a/docs/images/graphs/dep_graph_core_testing.svg b/docs/images/graphs/dep_graph_core_testing.svg index 3059e914b..ac1b301aa 100644 --- a/docs/images/graphs/dep_graph_core_testing.svg +++ b/docs/images/graphs/dep_graph_core_testing.svg @@ -1,187 +1,163 @@ - - - - - -G - - - -:core:testing - -:core:testing - - - -:core:analytics - -:core:analytics - - - -:core:testing->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:testing->:core:common - - - - - -:core:data - -:core:data - - - -:core:testing->:core:data - - - - - -:core:model - -:core:model - - - -:core:testing->:core:model - - - - - -:core:notifications - -:core:notifications - - - -:core:testing->:core:notifications - - - - - -:core:data->:core:analytics - - - - - -:core:data->:core:common - - - - - -:core:data->:core:notifications - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - + + + + G + + + :core:testing + + :core:testing + + + :core:analytics + + :core:analytics + + + :core:testing->:core:analytics + + + + + :core:data + + :core:data + + + :core:testing->:core:data + + + + + :core:model + + :core:model + + + :core:testing->:core:model + + + + + :core:notifications + + :core:notifications + + + :core:testing->:core:notifications + + + + + :core:common + + :core:common + + + :core:testing->:core:common + + + + + :core:designsystem + + :core:designsystem + + + :core:testing->:core:designsystem + + + + + :core:data->:core:analytics + + + + + :core:data->:core:notifications + + + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_core_ui.svg b/docs/images/graphs/dep_graph_core_ui.svg index 2eba46866..a36dd7031 100644 --- a/docs/images/graphs/dep_graph_core_ui.svg +++ b/docs/images/graphs/dep_graph_core_ui.svg @@ -1,55 +1,43 @@ - - - - - -G - - - -:core:ui - -:core:ui - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:designsystem - -:core:designsystem - - - -:core:ui->:core:designsystem - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - + + + + G + + + :core:ui + + :core:ui + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:designsystem + + :core:designsystem + + + :core:ui->:core:designsystem + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_feature_bookmarks.svg b/docs/images/graphs/dep_graph_feature_bookmarks.svg index cfbb86412..8fbe423c2 100644 --- a/docs/images/graphs/dep_graph_feature_bookmarks.svg +++ b/docs/images/graphs/dep_graph_feature_bookmarks.svg @@ -1,205 +1,168 @@ - - - - - -G - - - -:feature:bookmarks - -:feature:bookmarks - - - -:core:ui - -:core:ui - - - -:feature:bookmarks->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:bookmarks->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:bookmarks->:core:data - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:bookmarks + + :feature:bookmarks + + + :core:ui + + :core:ui + + + :feature:bookmarks->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:bookmarks->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:bookmarks->:core:data + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_feature_foryou.svg b/docs/images/graphs/dep_graph_feature_foryou.svg index e196bc7da..6b8af3764 100644 --- a/docs/images/graphs/dep_graph_feature_foryou.svg +++ b/docs/images/graphs/dep_graph_feature_foryou.svg @@ -1,229 +1,188 @@ - - - - - -G - - - -:feature:foryou - -:feature:foryou - - - -:core:ui - -:core:ui - - - -:feature:foryou->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:foryou->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:foryou->:core:data - - - - - -:core:domain - -:core:domain - - - -:feature:foryou->:core:domain - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:domain->:core:data - - - - - -:core:domain->:core:model - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:foryou + + :feature:foryou + + + :core:ui + + :core:ui + + + :feature:foryou->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:foryou->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:foryou->:core:data + + + + + :core:domain + + :core:domain + + + :feature:foryou->:core:domain + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:domain->:core:data + + + + + :core:domain->:core:model + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_feature_interests.svg b/docs/images/graphs/dep_graph_feature_interests.svg index 3728cb4f6..b13783a27 100644 --- a/docs/images/graphs/dep_graph_feature_interests.svg +++ b/docs/images/graphs/dep_graph_feature_interests.svg @@ -1,229 +1,188 @@ - - - - - -G - - - -:feature:interests - -:feature:interests - - - -:core:ui - -:core:ui - - - -:feature:interests->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:interests->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:interests->:core:data - - - - - -:core:domain - -:core:domain - - - -:feature:interests->:core:domain - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:domain->:core:data - - - - - -:core:domain->:core:model - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:interests + + :feature:interests + + + :core:ui + + :core:ui + + + :feature:interests->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:interests->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:interests->:core:data + + + + + :core:domain + + :core:domain + + + :feature:interests->:core:domain + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:domain->:core:data + + + + + :core:domain->:core:model + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_feature_search.svg b/docs/images/graphs/dep_graph_feature_search.svg index 24c90cb0c..ff299f33f 100644 --- a/docs/images/graphs/dep_graph_feature_search.svg +++ b/docs/images/graphs/dep_graph_feature_search.svg @@ -1,229 +1,188 @@ - - - - - -G - - - -:feature:search - -:feature:search - - - -:core:ui - -:core:ui - - - -:feature:search->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:search->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:search->:core:data - - - - - -:core:domain - -:core:domain - - - -:feature:search->:core:domain - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:domain->:core:data - - - - - -:core:domain->:core:model - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:search + + :feature:search + + + :core:ui + + :core:ui + + + :feature:search->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:search->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:search->:core:data + + + + + :core:domain + + :core:domain + + + :feature:search->:core:domain + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:domain->:core:data + + + + + :core:domain->:core:model + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_feature_settings.svg b/docs/images/graphs/dep_graph_feature_settings.svg index 93826715a..1b9648d8b 100644 --- a/docs/images/graphs/dep_graph_feature_settings.svg +++ b/docs/images/graphs/dep_graph_feature_settings.svg @@ -1,205 +1,168 @@ - - - - - -G - - - -:feature:settings - -:feature:settings - - - -:core:ui - -:core:ui - - - -:feature:settings->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:settings->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:settings->:core:data - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:settings + + :feature:settings + + + :core:ui + + :core:ui + + + :feature:settings->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:settings->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:settings->:core:data + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_feature_topic.svg b/docs/images/graphs/dep_graph_feature_topic.svg index cbda3c225..49fe361a8 100644 --- a/docs/images/graphs/dep_graph_feature_topic.svg +++ b/docs/images/graphs/dep_graph_feature_topic.svg @@ -1,205 +1,168 @@ - - - - - -G - - - -:feature:topic - -:feature:topic - - - -:core:ui - -:core:ui - - - -:feature:topic->:core:ui - - - - - -:core:designsystem - -:core:designsystem - - - -:feature:topic->:core:designsystem - - - - - -:core:data - -:core:data - - - -:feature:topic->:core:data - - - - - -:core:ui->:core:designsystem - - - - - -:core:analytics - -:core:analytics - - - -:core:ui->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:ui->:core:model - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:database->:core:model - - - - - -:core:datastore->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:model - - - - - -:core:network->:core:common - - - - - -:core:notifications->:core:model - - - - - -:core:notifications->:core:common - - - - + + + + G + + + :feature:topic + + :feature:topic + + + :core:ui + + :core:ui + + + :feature:topic->:core:ui + + + + + :core:designsystem + + :core:designsystem + + + :feature:topic->:core:designsystem + + + + + :core:data + + :core:data + + + :feature:topic->:core:data + + + + + :core:ui->:core:designsystem + + + + + :core:analytics + + :core:analytics + + + :core:ui->:core:analytics + + + + + :core:model + + :core:model + + + :core:ui->:core:model + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:database->:core:model + + + + + :core:datastore->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:model + + + + + :core:network->:core:common + + + + + :core:notifications->:core:model + + + + + :core:notifications->:core:common + + + + diff --git a/docs/images/graphs/dep_graph_sync_sync_test.svg b/docs/images/graphs/dep_graph_sync_sync_test.svg index 1e0753393..58a31af19 100644 --- a/docs/images/graphs/dep_graph_sync_sync_test.svg +++ b/docs/images/graphs/dep_graph_sync_sync_test.svg @@ -1,187 +1,153 @@ - - - - - -G - - - -:sync:sync-test - -:sync:sync-test - - - -:core:data - -:core:data - - - -:sync:sync-test->:core:data - - - - - -:sync:work - -:sync:work - - - -:sync:sync-test->:sync:work - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:analytics - -:core:analytics - - - -:core:data->:core:analytics - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:sync:work->:core:data - - - - - -:sync:work->:core:analytics - - - - - -:core:model - -:core:model - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - + + + + G + + + :sync:sync-test + + :sync:sync-test + + + :core:data + + :core:data + + + :sync:sync-test->:core:data + + + + + :sync:work + + :sync:work + + + :sync:sync-test->:sync:work + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:analytics + + :core:analytics + + + :core:data->:core:analytics + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :sync:work->:core:data + + + + + :sync:work->:core:analytics + + + + + :core:model + + :core:model + + + :core:database->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore->:core:model + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:common + + + + + :core:network->:core:model + + + + + :core:notifications->:core:common + + + + + :core:notifications->:core:model + + + + diff --git a/docs/images/graphs/dep_graph_sync_work.svg b/docs/images/graphs/dep_graph_sync_work.svg index 6901b5761..2581a10ae 100644 --- a/docs/images/graphs/dep_graph_sync_work.svg +++ b/docs/images/graphs/dep_graph_sync_work.svg @@ -1,169 +1,138 @@ - - - - - -G - - - -:sync:work - -:sync:work - - - -:core:analytics - -:core:analytics - - - -:sync:work->:core:analytics - - - - - -:core:data - -:core:data - - - -:sync:work->:core:data - - - - - -:core:data->:core:analytics - - - - - -:core:common - -:core:common - - - -:core:data->:core:common - - - - - -:core:database - -:core:database - - - -:core:data->:core:database - - - - - -:core:datastore - -:core:datastore - - - -:core:data->:core:datastore - - - - - -:core:network - -:core:network - - - -:core:data->:core:network - - - - - -:core:notifications - -:core:notifications - - - -:core:data->:core:notifications - - - - - -:core:model - -:core:model - - - -:core:database->:core:model - - - - - -:core:datastore->:core:common - - - - - -:core:datastore->:core:model - - - - - -:core:datastore-proto - -:core:datastore-proto - - - -:core:datastore->:core:datastore-proto - - - - - -:core:network->:core:common - - - - - -:core:network->:core:model - - - - - -:core:notifications->:core:common - - - - - -:core:notifications->:core:model - - - - + + + + G + + + :sync:work + + :sync:work + + + :core:analytics + + :core:analytics + + + :sync:work->:core:analytics + + + + + :core:data + + :core:data + + + :sync:work->:core:data + + + + + :core:data->:core:analytics + + + + + :core:common + + :core:common + + + :core:data->:core:common + + + + + :core:database + + :core:database + + + :core:data->:core:database + + + + + :core:datastore + + :core:datastore + + + :core:data->:core:datastore + + + + + :core:network + + :core:network + + + :core:data->:core:network + + + + + :core:notifications + + :core:notifications + + + :core:data->:core:notifications + + + + + :core:model + + :core:model + + + :core:database->:core:model + + + + + :core:datastore->:core:common + + + + + :core:datastore->:core:model + + + + + :core:datastore-proto + + :core:datastore-proto + + + :core:datastore->:core:datastore-proto + + + + + :core:network->:core:common + + + + + :core:network->:core:model + + + + + :core:notifications->:core:common + + + + + :core:notifications->:core:model + + + + diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt index ad6ca6112..6c2af240c 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt @@ -23,9 +23,9 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository +import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase -import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsCountUseCase import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase import com.google.samples.apps.nowinandroid.core.model.data.UserSearchResult import dagger.hilt.android.lifecycle.HiltViewModel @@ -42,8 +42,8 @@ import javax.inject.Inject @HiltViewModel class SearchViewModel @Inject constructor( getSearchContentsUseCase: GetSearchContentsUseCase, - getSearchContentsCountUseCase: GetSearchContentsCountUseCase, recentSearchQueriesUseCase: GetRecentSearchQueriesUseCase, + private val searchContentsRepository: SearchContentsRepository, private val recentSearchRepository: RecentSearchRepository, private val userDataRepository: UserDataRepository, private val savedStateHandle: SavedStateHandle, @@ -53,7 +53,7 @@ class SearchViewModel @Inject constructor( val searchQuery = savedStateHandle.getStateFlow(key = SEARCH_QUERY, initialValue = "") val searchResultUiState: StateFlow = - getSearchContentsCountUseCase() + searchContentsRepository.getSearchContentsCount() .flatMapLatest { totalCount -> if (totalCount < SEARCH_MIN_FTS_ENTITY_COUNT) { flowOf(SearchResultUiState.SearchNotReady) diff --git a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt index 662afca7d..c832401de 100644 --- a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt +++ b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt @@ -19,7 +19,6 @@ package com.google.samples.apps.nowinandroid.feature.search import androidx.lifecycle.SavedStateHandle import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase -import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsCountUseCase import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData import com.google.samples.apps.nowinandroid.core.testing.data.topicsTestData @@ -60,7 +59,6 @@ class SearchViewModelTest { ) private val recentSearchRepository = TestRecentSearchRepository() private val getRecentQueryUseCase = GetRecentSearchQueriesUseCase(recentSearchRepository) - private val getSearchContentsCountUseCase = GetSearchContentsCountUseCase(searchContentsRepository) private lateinit var viewModel: SearchViewModel @@ -68,8 +66,8 @@ class SearchViewModelTest { fun setup() { viewModel = SearchViewModel( getSearchContentsUseCase = getSearchContentsUseCase, - getSearchContentsCountUseCase = getSearchContentsCountUseCase, recentSearchQueriesUseCase = getRecentQueryUseCase, + searchContentsRepository = searchContentsRepository, savedStateHandle = SavedStateHandle(), recentSearchRepository = recentSearchRepository, userDataRepository = userDataRepository, @@ -87,7 +85,8 @@ class SearchViewModelTest { fun stateIsEmptyQuery_withEmptySearchQuery() = runTest { searchContentsRepository.addNewsResources(newsResourcesTestData) searchContentsRepository.addTopics(topicsTestData) - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("") @@ -98,7 +97,8 @@ class SearchViewModelTest { @Test fun emptyResultIsReturned_withNotMatchingQuery() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("XXX") searchContentsRepository.addNewsResources(newsResourcesTestData) @@ -112,7 +112,8 @@ class SearchViewModelTest { @Test fun recentSearches_verifyUiStateIsSuccess() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() } viewModel.onSearchTriggered("kotlin") val result = viewModel.recentSearchQueriesUiState.value @@ -123,7 +124,8 @@ class SearchViewModelTest { @Test fun searchNotReady_withNoFtsTableEntity() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("") diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt index 41804b634..394c53303 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt @@ -41,13 +41,16 @@ internal class TopicArgs(val topicId: String) { } fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) { - val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) - val newRoute = "$TOPIC_ROUTE/$encodedId" - navigate(newRoute) { + navigate(createTopicRoute(topicId)) { navOptions() } } +fun createTopicRoute(topicId: String): String { + val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) + return "$TOPIC_ROUTE/$encodedId" +} + fun NavGraphBuilder.topicScreen( showBackButton: Boolean, onBackClick: () -> Unit, diff --git a/generateModuleGraphs.sh b/generateModuleGraphs.sh index fb2d74712..3a826586b 100755 --- a/generateModuleGraphs.sh +++ b/generateModuleGraphs.sh @@ -100,8 +100,11 @@ echo "$module_paths" | while read -r module_path; do -Pmodules.graph.output.gv="/tmp/${file_name}.gv" \ -Pmodules.graph.of.module="${module_path}" "docs/images/graphs/${file_name}.svg" + # Convert to SVG using dot, remove unnecessary comments, and reformat + dot -Tsvg "/tmp/${file_name}.gv" | + sed 's//-->\x0/g' | grep -zv '^