From 44cd995d3d82511472edff18e54e57081f9d87a7 Mon Sep 17 00:00:00 2001 From: Arash Ebrahimzade <68777571+aarash709@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:21:26 +0330 Subject: [PATCH 01/23] Updated screenshot tests link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6f13f5de2..be1270b16 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,9 @@ tests against _all_ build variants which is both unecessary and will result in f A screenshot test takes a screenshot of a screen or a UI component within the app, and compares it with a previously recorded screenshot which is known to be rendered correctly. -For example, Now in Android has [screenshot tests](https://github.com/android/nowinandroid/blob/main/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt) +For example, Now in Android has [screenshot tests](https://github.com/android/nowinandroid/blob/main/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt) to verify that the navigation is displayed correctly on different screen sizes -([known correct screenshots](https://github.com/android/nowinandroid/tree/main/app/src/testDemoDebug/screenshots)). +([known correct screenshots](https://github.com/android/nowinandroid/tree/main/app/src/testDemo/screenshots)). Now In Android uses [Roborazzi](https://github.com/takahirom/roborazzi) to run screenshot tests of certain screens and UI components. When working with screenshot tests the following gradle tasks are useful: From d932cde11049e4f457c1db33382f81c96dfd2efc Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 27 Jan 2024 10:05:21 +0000 Subject: [PATCH 02/23] Move `androidx.compose.ui:ui-tooling` dependency to convention plugin --- .../com/google/samples/apps/nowinandroid/AndroidCompose.kt | 1 + core/designsystem/build.gradle.kts | 3 --- gradle/libs.versions.toml | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) 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 039987cac..6e08c2658 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 @@ -41,6 +41,7 @@ internal fun Project.configureAndroidCompose( val bom = libs.findLibrary("androidx-compose-bom").get() add("implementation", platform(bom)) add("androidTestImplementation", platform(bom)) + add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get()) } testOptions { diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index d68117d06..75e59f255 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -35,11 +35,8 @@ dependencies { api(libs.androidx.compose.material.iconsExtended) api(libs.androidx.compose.material3) api(libs.androidx.compose.runtime) - api(libs.androidx.compose.ui.tooling.preview) api(libs.androidx.compose.ui.util) - debugApi(libs.androidx.compose.ui.tooling) - implementation(libs.coil.kt.compose) testImplementation(libs.androidx.compose.ui.test) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 795510bce..ab11f9279 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,7 +75,6 @@ androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" } From db4bf1c78286428892e9aff2701f8b4dd37c1036 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 27 Jan 2024 10:25:24 +0000 Subject: [PATCH 03/23] Keep `androidx.compose.ui:ui-tooling-preview` on all buildTypes --- .../com/google/samples/apps/nowinandroid/AndroidCompose.kt | 1 + gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+) 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 6e08c2658..a4b3d0501 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 @@ -41,6 +41,7 @@ internal fun Project.configureAndroidCompose( val bom = libs.findLibrary("androidx-compose-bom").get() add("implementation", platform(bom)) add("androidTestImplementation", platform(bom)) + add("implementation", libs.findLibrary("androidx-compose-ui-tooling-preview").get()) add("debugImplementation", libs.findLibrary("androidx-compose-ui-tooling").get()) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab11f9279..795510bce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -75,6 +75,7 @@ androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" } From 20c8db6b0c1a2f9bd71bce5f22aecf82ee93cce4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 08:18:10 +0000 Subject: [PATCH 04/23] Bump gradle/gradle-build-action from 2 to 3 Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2 to 3. - [Release notes](https://github.com/gradle/gradle-build-action/releases) - [Commits](https://github.com/gradle/gradle-build-action/compare/v2...v3) --- updated-dependencies: - dependency-name: gradle/gradle-build-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 9bd84abc0..76b85a5a2 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -37,7 +37,7 @@ jobs: java-version: 17 - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 - name: Check build-logic run: ./gradlew check -p build-logic @@ -144,7 +144,7 @@ jobs: java-version: 17 - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v3 - name: Build projects before running emulator run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest From 894e817f8f88d076d09613d26c59e29868dd9570 Mon Sep 17 00:00:00 2001 From: yongsuk Date: Wed, 31 Jan 2024 18:52:53 +0900 Subject: [PATCH 05/23] Update typo setup.sh --- tools/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/setup.sh b/tools/setup.sh index 1467bbad0..b0f204268 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -35,7 +35,7 @@ cp "${GIT_ROOT}/tools/pre-push" "${GIT_DIR}/hooks/pre-push" \ cat <<-EOF Checking the following settings helps avoid miscellaneous issues: * Settings -> Editor -> General -> Remove trailing spaces on: Modified lines - * Settings -> Editor -> General -> Ensure every file ends with a line break + * Settings -> Editor -> General -> Ensure every saved file ends with a line break * Settings -> Editor -> General -> Auto Import -> Optimize imports on the fly (for both Kotlin\ and Java) EOF From 3d339e1658dcf9c55ae37ac24ff4c3c175fc3556 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 6 Jan 2024 10:50:26 +0100 Subject: [PATCH 06/23] Move `:core:data` fake repositories to `:core:data-test` Now that these fake implementations are no longer used in the production source code (only in tests), we can safely extract them out of this module. Hopefully, we'll later be able to use Kotlin testFixtures for that. References: - #709 - #875 --- .../nowinandroid/core/data/test/TestDataModule.kt | 12 ++++++------ .../core/data/test/repository}/FakeNewsRepository.kt | 4 ++-- .../test/repository}/FakeRecentSearchRepository.kt | 4 ++-- .../test/repository}/FakeSearchContentsRepository.kt | 4 ++-- .../data/test/repository}/FakeTopicsRepository.kt | 4 ++-- .../data/test/repository}/FakeUserDataRepository.kt | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) rename core/{data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake => data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository}/FakeNewsRepository.kt (96%) rename core/{data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake => data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository}/FakeRecentSearchRepository.kt (88%) rename core/{data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake => data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository}/FakeSearchContentsRepository.kt (87%) rename core/{data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake => data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository}/FakeTopicsRepository.kt (94%) rename core/{data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake => data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository}/FakeUserDataRepository.kt (95%) diff --git a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt index 2ec2bcf9c..0ae4865ec 100644 --- a/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt @@ -22,11 +22,11 @@ import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRep import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository 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.fake.FakeNewsRepository -import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeRecentSearchRepository -import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeSearchContentsRepository -import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeTopicsRepository -import com.google.samples.apps.nowinandroid.core.data.repository.fake.FakeUserDataRepository +import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeNewsRepository +import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeRecentSearchRepository +import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeSearchContentsRepository +import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeTopicsRepository +import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import dagger.Binds import dagger.Module @@ -38,7 +38,7 @@ import dagger.hilt.testing.TestInstallIn components = [SingletonComponent::class], replaces = [DataModule::class], ) -interface TestDataModule { +internal interface TestDataModule { @Binds fun bindsTopicRepository( fakeTopicsRepository: FakeTopicsRepository, diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeNewsRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt similarity index 96% rename from core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeNewsRepository.kt rename to core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt index 39ad05d1e..5ceff4dd0 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeNewsRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.data.repository.fake +package com.google.samples.apps.nowinandroid.core.data.test.repository import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.model.asEntity @@ -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. */ -class FakeNewsRepository @Inject constructor( +internal class FakeNewsRepository @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, private val datasource: FakeNiaNetworkDataSource, ) : NewsRepository { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeRecentSearchRepository.kt similarity index 88% rename from core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt rename to core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeRecentSearchRepository.kt index 025b51f68..b8d949efe 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeRecentSearchRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.data.repository.fake +package com.google.samples.apps.nowinandroid.core.data.test.repository import com.google.samples.apps.nowinandroid.core.data.model.RecentSearchQuery import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository @@ -25,7 +25,7 @@ import javax.inject.Inject /** * Fake implementation of the [RecentSearchRepository] */ -class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository { +internal class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository { override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit override fun getRecentSearchQueries(limit: Int): Flow> = diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeSearchContentsRepository.kt similarity index 87% rename from core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt rename to core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeSearchContentsRepository.kt index 65cced452..1feeb6dcc 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeSearchContentsRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.data.repository.fake +package com.google.samples.apps.nowinandroid.core.data.test.repository import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository import com.google.samples.apps.nowinandroid.core.model.data.SearchResult @@ -25,7 +25,7 @@ import javax.inject.Inject /** * Fake implementation of the [SearchContentsRepository] */ -class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { +internal class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { override suspend fun populateFtsData() = Unit override fun searchContents(searchQuery: String): Flow = flowOf() diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeTopicsRepository.kt similarity index 94% rename from core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt rename to core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeTopicsRepository.kt index 0eefc8451..f8ebca29e 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeTopicsRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.data.repository.fake +package com.google.samples.apps.nowinandroid.core.data.test.repository import com.google.samples.apps.nowinandroid.core.data.Synchronizer import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository @@ -36,7 +36,7 @@ import javax.inject.Inject * This allows us to run the app with fake data, without needing an internet connection or working * backend. */ -class FakeTopicsRepository @Inject constructor( +internal class FakeTopicsRepository @Inject constructor( @Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher, private val datasource: FakeNiaNetworkDataSource, ) : TopicsRepository { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt similarity index 95% rename from core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt rename to core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt index a9da29b56..cdd23429f 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeUserDataRepository.kt +++ b/core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeUserDataRepository.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.data.repository.fake +package com.google.samples.apps.nowinandroid.core.data.test.repository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.datastore.NiaPreferencesDataSource @@ -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. */ -class FakeUserDataRepository @Inject constructor( +internal class FakeUserDataRepository @Inject constructor( private val niaPreferencesDataSource: NiaPreferencesDataSource, ) : UserDataRepository { From 9b488bf70ab1b3ceded4f7a3c6071ac9e2748919 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Sun, 3 Mar 2024 13:57:03 +0900 Subject: [PATCH 07/23] Move java to kotlin folder. Change-Id: Iaf4723dea6169fbf3f9a8d3bf8779179ac56ab86 --- .../samples/apps/nowinandroid/util/ProfileVerifierLogger.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/{java => kotlin}/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt (100%) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt similarity index 100% rename from app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt rename to app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt From c90804f198d1a272c19494403c6a661cca8098c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:00:57 +0000 Subject: [PATCH 08/23] Bump com.google.truth:truth from 1.1.5 to 1.4.2 Bumps [com.google.truth:truth](https://github.com/google/truth) from 1.1.5 to 1.4.2. - [Release notes](https://github.com/google/truth/releases) - [Commits](https://github.com/google/truth/compare/v1.1.5...v1.4.2) --- updated-dependencies: - dependency-name: com.google.truth:truth dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c26e134ca..5c4c3b0f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ robolectric = "4.11.1" roborazzi = "1.7.0" room = "2.6.1" secrets = "2.0.1" -truth = "1.1.5" +truth = "1.4.2" turbine = "1.0.0" [libraries] From 6b6bba9db3b6926d3c5765647a7bef2815290a30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:01:16 +0000 Subject: [PATCH 09/23] Bump hilt from 2.50 to 2.51 Bumps `hilt` from 2.50 to 2.51. Updates `com.google.dagger:hilt-android` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-android-testing` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger:hilt-android-compiler` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) Updates `com.google.dagger.hilt.android` from 2.50 to 2.51 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51) --- updated-dependencies: - dependency-name: com.google.dagger:hilt-android dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-testing dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger.hilt.android dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c26e134ca..18586cb35 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -hilt = "2.50" +hilt = "2.51" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" From b2b9f12915cbaf8681125ea3f55ff3398e14f390 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 4 Mar 2024 09:04:03 +0000 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=A4=96=20Updates=20baselines=20for?= =?UTF-8?q?=20Dependency=20Guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app-nia-catalog/dependencies/releaseRuntimeClasspath.txt | 8 ++++---- app/dependencies/prodReleaseRuntimeClasspath.txt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 182ff5451..f51d155e9 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -80,10 +80,10 @@ androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.code.findbugs:jsr305:3.0.2 -com.google.dagger:dagger-lint-aar:2.50 -com.google.dagger:dagger:2.50 -com.google.dagger:hilt-android:2.50 -com.google.dagger:hilt-core:2.50 +com.google.dagger:dagger-lint-aar:2.51 +com.google.dagger:dagger:2.51 +com.google.dagger:hilt-android:2.51 +com.google.dagger:hilt-core:2.51 com.google.guava:listenablefuture:1.0 com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.6.0 diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index e7a4ac804..81b7343dd 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -137,10 +137,10 @@ com.google.android.gms:play-services-oss-licenses:17.0.1 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.0.2 com.google.code.findbugs:jsr305:3.0.2 -com.google.dagger:dagger-lint-aar:2.50 -com.google.dagger:dagger:2.50 -com.google.dagger:hilt-android:2.50 -com.google.dagger:hilt-core:2.50 +com.google.dagger:dagger-lint-aar:2.51 +com.google.dagger:dagger:2.51 +com.google.dagger:hilt-android:2.51 +com.google.dagger:hilt-core:2.51 com.google.errorprone:error_prone_annotations:2.11.0 com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-analytics-ktx:21.4.0 From eb6b344463304756d6f418547897931af1eaed88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:07:19 +0000 Subject: [PATCH 11/23] Bump the kotlin-ksp-compose group with 6 updates Bumps the kotlin-ksp-compose group with 6 updates: | Package | From | To | | --- | --- | --- | | [org.jetbrains.kotlin:kotlin-stdlib-jdk8](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [org.jetbrains.kotlin.plugin.serialization](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) | `1.9.21` | `1.9.22` | | [com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin](https://github.com/google/ksp) | `1.9.21-1.0.16` | `1.9.22-1.0.18` | | [com.google.devtools.ksp](https://github.com/google/ksp) | `1.9.21-1.0.16` | `1.9.22-1.0.18` | Updates `org.jetbrains.kotlin:kotlin-stdlib-jdk8` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `org.jetbrains.kotlin.jvm` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `org.jetbrains.kotlin.plugin.serialization` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `org.jetbrains.kotlin.jvm` from 1.9.21 to 1.9.22 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.21...v1.9.22) Updates `com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin` from 1.9.21-1.0.16 to 1.9.22-1.0.18 - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/commits) Updates `com.google.devtools.ksp` from 1.9.21-1.0.16 to 1.9.22-1.0.18 - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/commits) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-stdlib-jdk8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: org.jetbrains.kotlin.jvm dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: org.jetbrains.kotlin.plugin.serialization dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: org.jetbrains.kotlin.jvm dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - dependency-name: com.google.devtools.ksp dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c26e134ca..dab6c3ab2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,11 +40,11 @@ hilt = "2.50" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" -kotlin = "1.9.21" +kotlin = "1.9.22" kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" -ksp = "1.9.21-1.0.16" +ksp = "1.9.22-1.0.18" okhttp = "4.12.0" protobuf = "3.25.2" protobufPlugin = "0.9.4" From 6996c88e91d4d92254fa326976987d85f8d6e52b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 4 Mar 2024 09:10:40 +0000 Subject: [PATCH 12/23] =?UTF-8?q?=F0=9F=A4=96=20Updates=20baselines=20for?= =?UTF-8?q?=20Dependency=20Guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app-nia-catalog/dependencies/releaseRuntimeClasspath.txt | 4 ++-- app/dependencies/prodReleaseRuntimeClasspath.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 182ff5451..2c921f091 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -93,10 +93,10 @@ io.coil-kt:coil-compose-base:2.5.0 io.coil-kt:coil-compose:2.5.0 io.coil-kt:coil:2.5.0 javax.inject:javax.inject:1 -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 -org.jetbrains.kotlin:kotlin-stdlib:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib:1.9.22 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index e7a4ac804..70262d4ac 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -188,10 +188,10 @@ io.github.aakira:napier-android:1.4.1 io.github.aakira:napier:1.4.1 javax.inject:javax.inject:1 org.checkerframework:checker-qual:3.12.0 -org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 -org.jetbrains.kotlin:kotlin-stdlib:1.9.21 +org.jetbrains.kotlin:kotlin-stdlib:1.9.22 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 From 52c0d7ae0a7b48fb2b8498c952d8ac41c7e31d17 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Mon, 4 Mar 2024 19:51:12 +0900 Subject: [PATCH 13/23] Remove Insert function - Insert is only used in test. - Upsert do same thing as Insert. Change-Id: Ib5f8e693e71c895b83e43f6b51cebdc698bf744b --- .../core/data/testdoubles/TestNewsResourceDao.kt | 15 --------------- .../core/database/dao/NewsResourceDao.kt | 6 ------ 2 files changed, 21 deletions(-) diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt index dc4b78e01..a3e373918 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt @@ -92,21 +92,6 @@ class TestNewsResourceDao : NewsResourceDao { result.map { it.entity.id } } - override suspend fun insertOrIgnoreNewsResources( - entities: List, - ): List { - entitiesStateFlow.update { oldValues -> - // Old values come first so new values don't overwrite them - (oldValues + entities) - .distinctBy(NewsResourceEntity::id) - .sortedWith( - compareBy(NewsResourceEntity::publishDate).reversed(), - ) - } - // Assume no conflicts on insert - return entities.map { it.id.toLong() } - } - override suspend fun upsertNewsResources(newsResourceEntities: List) { entitiesStateFlow.update { oldValues -> // New values come first so they overwrite old values diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt index 0ad1e4f7d..929b88ce6 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDao.kt @@ -96,12 +96,6 @@ interface NewsResourceDao { filterNewsIds: Set = emptySet(), ): Flow> - /** - * Inserts [entities] into the db if they don't exist, and ignores those that do - */ - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertOrIgnoreNewsResources(entities: List): List - /** * Inserts or updates [newsResourceEntities] in the db under the specified primary keys */ From 9cd430ba7d6b48d1ee155a055972752048610d86 Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Tue, 2 Jan 2024 16:31:21 -0800 Subject: [PATCH 14/23] Update AGP to 8.3.0 Change-Id: I6ea5aeaf357dafbae7be0e7f5f0443fbb1a8e68b --- .../com/google/samples/apps/nowinandroid/AndroidCompose.kt | 2 +- .../google/samples/apps/nowinandroid/GradleManagedDevices.kt | 2 +- .../com/google/samples/apps/nowinandroid/KotlinAndroid.kt | 2 +- .../kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt | 2 +- gradle/libs.versions.toml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) 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 72d37db1b..3284a2499 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 @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile * Configure Compose-specific options */ internal fun Project.configureAndroidCompose( - commonExtension: CommonExtension<*, *, *, *, *>, + commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { buildFeatures { diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt index 6aa896444..f67e9093d 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt @@ -25,7 +25,7 @@ import org.gradle.kotlin.dsl.invoke * Configure project for Gradle managed devices */ internal fun configureGradleManagedDevices( - commonExtension: CommonExtension<*, *, *, *, *>, + commonExtension: CommonExtension<*, *, *, *, *, *>, ) { val pixel4 = DeviceConfig("Pixel 4", 30, "aosp-atd") val pixel6 = DeviceConfig("Pixel 6", 31, "aosp") diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index 903c84d8f..f9a6717c3 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile * Configure base Kotlin with Android options */ internal fun Project.configureKotlinAndroid( - commonExtension: CommonExtension<*, *, *, *, *>, + commonExtension: CommonExtension<*, *, *, *, *, *>, ) { commonExtension.apply { compileSdk = 34 diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt index 60d059ac0..633098604 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt @@ -20,7 +20,7 @@ enum class NiaFlavor(val dimension: FlavorDimension, val applicationIdSuffix: St } fun configureFlavors( - commonExtension: CommonExtension<*, *, *, *, *>, + commonExtension: CommonExtension<*, *, *, *, *, *>, flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {} ) { commonExtension.apply { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3fafee176..680d0542b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ accompanist = "0.32.0" androidDesugarJdkLibs = "2.0.4" # AGP and tools should be updated together -androidGradlePlugin = "8.2.0" -androidTools = "31.2.0" +androidGradlePlugin = "8.3.0" +androidTools = "31.3.0" androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" From e3c7018124603a424245106f05f49ae5c269272f Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Mon, 4 Mar 2024 16:34:45 -0800 Subject: [PATCH 15/23] Extract ScreenshotHelper to separate testing module Change-Id: Icf98a4acc9963656530a1a9fc8b156a553a070af --- app/build.gradle.kts | 2 ++ core/designsystem/build.gradle.kts | 1 + core/screenshot-testing/.gitignore | 1 + core/screenshot-testing/build.gradle.kts | 34 +++++++++++++++++++ .../src/main/AndroidManifest.xml | 17 ++++++++++ .../core/testing/util/ScreenshotHelper.kt | 0 core/testing/build.gradle.kts | 4 --- feature/foryou/build.gradle.kts | 1 + gradle/libs.versions.toml | 1 - settings.gradle.kts | 7 ++-- 10 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 core/screenshot-testing/.gitignore create mode 100644 core/screenshot-testing/build.gradle.kts create mode 100644 core/screenshot-testing/src/main/AndroidManifest.xml rename core/{testing => screenshot-testing}/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d674f4bec..51fbb1c0d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,10 +113,12 @@ dependencies { testDemoImplementation(libs.robolectric) testDemoImplementation(libs.roborazzi) + testDemoImplementation(projects.core.screenshotTesting) androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.dataTest) androidTestImplementation(projects.core.datastoreTest) + androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.accompanist.testharness) androidTestImplementation(libs.hilt.android.testing) diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index d68117d06..2bd79a442 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) testImplementation(libs.roborazzi) + testImplementation(projects.core.screenshotTesting) testImplementation(projects.core.testing) androidTestImplementation(libs.androidx.compose.ui.test) diff --git a/core/screenshot-testing/.gitignore b/core/screenshot-testing/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/core/screenshot-testing/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/screenshot-testing/build.gradle.kts b/core/screenshot-testing/build.gradle.kts new file mode 100644 index 000000000..4e9a931b0 --- /dev/null +++ b/core/screenshot-testing/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + alias(libs.plugins.nowinandroid.android.library) + alias(libs.plugins.nowinandroid.android.library.compose) + alias(libs.plugins.nowinandroid.android.hilt) +} + +android { + namespace = "com.google.samples.apps.nowinandroid.core.screenshottesting" +} + +dependencies { + api(libs.roborazzi) + implementation(libs.accompanist.testharness) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.ui.test) + implementation(libs.robolectric) + implementation(projects.core.common) + implementation(projects.core.designsystem) +} diff --git a/core/screenshot-testing/src/main/AndroidManifest.xml b/core/screenshot-testing/src/main/AndroidManifest.xml new file mode 100644 index 000000000..51d0cfc2e --- /dev/null +++ b/core/screenshot-testing/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/core/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 similarity index 100% rename from core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt rename to core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index 275555d80..02729ceff 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -26,7 +26,6 @@ android { dependencies { api(kotlin("test")) api(libs.androidx.compose.ui.test) - api(libs.roborazzi) api(projects.core.analytics) api(projects.core.data) api(projects.core.model) @@ -34,13 +33,10 @@ dependencies { debugApi(libs.androidx.compose.ui.testManifest) - implementation(libs.accompanist.testharness) - implementation(libs.androidx.activity.compose) implementation(libs.androidx.test.rules) implementation(libs.hilt.android.testing) implementation(libs.kotlinx.coroutines.test) implementation(libs.kotlinx.datetime) - implementation(libs.robolectric.shadows) implementation(projects.core.common) implementation(projects.core.designsystem) } diff --git a/feature/foryou/build.gradle.kts b/feature/foryou/build.gradle.kts index da0a7ec5b..fd41d9a13 100644 --- a/feature/foryou/build.gradle.kts +++ b/feature/foryou/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) testImplementation(projects.core.testing) + testImplementation(projects.core.screenshotTesting) testDemoImplementation(libs.roborazzi) androidTestImplementation(projects.core.testing) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 680d0542b..17d482a75 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -128,7 +128,6 @@ protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlin-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerializationJson" } robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } -robolectric-shadows = { group = "org.robolectric", name = "shadows-framework", version.ref = "robolectric" } roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } diff --git a/settings.gradle.kts b/settings.gradle.kts index fa043c955..949dbfdd1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,6 +36,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include(":app") include(":app-nia-catalog") include(":benchmarks") +include(":core:analytics") include(":core:common") include(":core:data") include(":core:data-test") @@ -47,10 +48,10 @@ include(":core:designsystem") include(":core:domain") include(":core:model") include(":core:network") -include(":core:ui") -include(":core:testing") -include(":core:analytics") include(":core:notifications") +include(":core:screenshot-testing") +include(":core:testing") +include(":core:ui") include(":feature:foryou") include(":feature:interests") From 06104076b2f5b16ae65180a4e3f3b422468286b9 Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Mon, 4 Mar 2024 16:56:57 -0800 Subject: [PATCH 16/23] Add explicit guava-android dependency for :sync:work Change-Id: I3c2c201eb0669165e3594bedfea83bff29c28ce2 --- gradle/libs.versions.toml | 2 ++ sync/work/build.gradle.kts | 1 + 2 files changed, 3 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17d482a75..e7f00830a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,7 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" +guavaAndroid = "33.0.0-android" hilt = "2.51" hiltExt = "1.1.0" jacoco = "0.8.7" @@ -108,6 +109,7 @@ firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashly firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx" } google-oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "googleOss" } google-oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "googleOssPlugin" } +guava-android = { group = "com.google.guava", name = "guava", version.ref = "guavaAndroid" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } diff --git a/sync/work/build.gradle.kts b/sync/work/build.gradle.kts index 7e61c7389..f949dde9b 100644 --- a/sync/work/build.gradle.kts +++ b/sync/work/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(libs.hilt.ext.work) implementation(projects.core.analytics) implementation(projects.core.data) + implementation(libs.guava.android) prodImplementation(libs.firebase.cloud.messaging) prodImplementation(platform(libs.firebase.bom)) From 1d91231f49a9774e32e87516edaa579769721cd4 Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Mon, 4 Mar 2024 17:07:15 -0800 Subject: [PATCH 17/23] Remove kotlinx-coroutines-guava dependency from :sync:work Change-Id: Ic5abbd36436a7d8c6382cb02b4ebd7538f8ae1f6 --- gradle/libs.versions.toml | 2 -- sync/work/build.gradle.kts | 2 -- 2 files changed, 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7f00830a..17d482a75 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,6 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -guavaAndroid = "33.0.0-android" hilt = "2.51" hiltExt = "1.1.0" jacoco = "0.8.7" @@ -109,7 +108,6 @@ firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashly firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx" } google-oss-licenses = { group = "com.google.android.gms", name = "play-services-oss-licenses", version.ref = "googleOss" } google-oss-licenses-plugin = { group = "com.google.android.gms", name = "oss-licenses-plugin", version.ref = "googleOssPlugin" } -guava-android = { group = "com.google.guava", name = "guava", version.ref = "guavaAndroid" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } diff --git a/sync/work/build.gradle.kts b/sync/work/build.gradle.kts index f949dde9b..97e3eace2 100644 --- a/sync/work/build.gradle.kts +++ b/sync/work/build.gradle.kts @@ -34,13 +34,11 @@ dependencies { implementation(libs.hilt.ext.work) implementation(projects.core.analytics) implementation(projects.core.data) - implementation(libs.guava.android) prodImplementation(libs.firebase.cloud.messaging) prodImplementation(platform(libs.firebase.bom)) androidTestImplementation(libs.androidx.work.testing) androidTestImplementation(libs.hilt.android.testing) - androidTestImplementation(libs.kotlinx.coroutines.guava) androidTestImplementation(projects.core.testing) } From 3fa75a037ab63243ef09488d6410dd7fa6a0b6a6 Mon Sep 17 00:00:00 2001 From: kim-yongsuk Date: Tue, 5 Mar 2024 12:30:06 +0900 Subject: [PATCH 18/23] Apply Composable Parameter Ordering Guidelines Change-Id: Id456e91b5ce8bd5807c40df67c3deac008c61670 --- .../core/designsystem/component/Navigation.kt | 12 ++-- .../component/scrollbar/AppScrollbars.kt | 4 +- .../component/scrollbar/Scrollbar.kt | 6 +- .../feature/search/SearchScreen.kt | 67 ++++++++++--------- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt index 9f8f71f14..59f4f48a2 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Navigation.kt @@ -53,12 +53,12 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme fun RowScope.NiaNavigationBarItem( selected: Boolean, onClick: () -> Unit, - icon: @Composable () -> Unit, modifier: Modifier = Modifier, - selectedIcon: @Composable () -> Unit = icon, enabled: Boolean = true, - label: @Composable (() -> Unit)? = null, alwaysShowLabel: Boolean = true, + icon: @Composable () -> Unit, + selectedIcon: @Composable () -> Unit = icon, + label: @Composable (() -> Unit)? = null, ) { NavigationBarItem( selected = selected, @@ -117,12 +117,12 @@ fun NiaNavigationBar( fun NiaNavigationRailItem( selected: Boolean, onClick: () -> Unit, - icon: @Composable () -> Unit, modifier: Modifier = Modifier, - selectedIcon: @Composable () -> Unit = icon, enabled: Boolean = true, - label: @Composable (() -> Unit)? = null, alwaysShowLabel: Boolean = true, + icon: @Composable () -> Unit, + selectedIcon: @Composable () -> Unit = icon, + label: @Composable (() -> Unit)? = null, ) { NavigationRailItem( selected = selected, diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/AppScrollbars.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/AppScrollbars.kt index c8102073a..1086e280b 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/AppScrollbars.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/AppScrollbars.kt @@ -75,10 +75,10 @@ private const val SCROLLBAR_INACTIVE_TO_DORMANT_TIME_IN_MS = 2_000L */ @Composable fun ScrollableState.DraggableScrollbar( - modifier: Modifier = Modifier, state: ScrollbarState, orientation: Orientation, onThumbMoved: (Float) -> Unit, + modifier: Modifier = Modifier, ) { val interactionSource = remember { MutableInteractionSource() } Scrollbar( @@ -105,9 +105,9 @@ fun ScrollableState.DraggableScrollbar( */ @Composable fun ScrollableState.DecorativeScrollbar( - modifier: Modifier = Modifier, state: ScrollbarState, orientation: Orientation, + modifier: Modifier = Modifier, ) { val interactionSource = remember { MutableInteractionSource() } Scrollbar( diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/Scrollbar.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/Scrollbar.kt index 8c85e5be5..002f36b31 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/Scrollbar.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/Scrollbar.kt @@ -195,13 +195,13 @@ internal fun Orientation.valueOf(intOffset: IntOffset) = when (this) { */ @Composable fun Scrollbar( - modifier: Modifier = Modifier, orientation: Orientation, state: ScrollbarState, - minThumbSize: Dp = 40.dp, + modifier: Modifier = Modifier, interactionSource: MutableInteractionSource? = null, - thumb: @Composable () -> Unit, + minThumbSize: Dp = 40.dp, onThumbMoved: ((Float) -> Unit)? = null, + thumb: @Composable () -> Unit, ) { // Using Offset.Unspecified and Float.NaN instead of null // to prevent unnecessary boxing of primitives diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt index d05f02b22..ca159c80b 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt @@ -100,10 +100,10 @@ import com.google.samples.apps.nowinandroid.feature.search.R as searchR @Composable internal fun SearchRoute( - modifier: Modifier = Modifier, onBackClick: () -> Unit, onInterestsClick: () -> Unit, onTopicClick: (String) -> Unit, + modifier: Modifier = Modifier, bookmarksViewModel: BookmarksViewModel = hiltViewModel(), interestsViewModel: InterestsViewModel = hiltViewModel(), searchViewModel: SearchViewModel = hiltViewModel(), @@ -114,36 +114,36 @@ internal fun SearchRoute( val searchQuery by searchViewModel.searchQuery.collectAsStateWithLifecycle() SearchScreen( modifier = modifier, - onBackClick = onBackClick, - onClearRecentSearches = searchViewModel::clearRecentSearches, - onFollowButtonClick = interestsViewModel::followTopic, - onInterestsClick = onInterestsClick, + searchQuery = searchQuery, + recentSearchesUiState = recentSearchQueriesUiState, + searchResultUiState = searchResultUiState, onSearchQueryChanged = searchViewModel::onSearchQueryChanged, onSearchTriggered = searchViewModel::onSearchTriggered, - onTopicClick = onTopicClick, + onClearRecentSearches = searchViewModel::clearRecentSearches, onNewsResourcesCheckedChanged = forYouViewModel::updateNewsResourceSaved, onNewsResourceViewed = { bookmarksViewModel.setNewsResourceViewed(it, true) }, - recentSearchesUiState = recentSearchQueriesUiState, - searchQuery = searchQuery, - searchResultUiState = searchResultUiState, + onFollowButtonClick = interestsViewModel::followTopic, + onBackClick = onBackClick, + onInterestsClick = onInterestsClick, + onTopicClick = onTopicClick, ) } @Composable internal fun SearchScreen( modifier: Modifier = Modifier, - onBackClick: () -> Unit = {}, + searchQuery: String = "", + recentSearchesUiState: RecentSearchQueriesUiState = RecentSearchQueriesUiState.Loading, + searchResultUiState: SearchResultUiState = SearchResultUiState.Loading, + onSearchQueryChanged: (String) -> Unit = {}, + onSearchTriggered: (String) -> Unit = {}, onClearRecentSearches: () -> Unit = {}, - onFollowButtonClick: (String, Boolean) -> Unit = { _, _ -> }, - onInterestsClick: () -> Unit = {}, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit = { _, _ -> }, onNewsResourceViewed: (String) -> Unit = {}, - onSearchQueryChanged: (String) -> Unit = {}, - onSearchTriggered: (String) -> Unit = {}, + onFollowButtonClick: (String, Boolean) -> Unit = { _, _ -> }, + onBackClick: () -> Unit = {}, + onInterestsClick: () -> Unit = {}, onTopicClick: (String) -> Unit = {}, - searchQuery: String = "", - recentSearchesUiState: RecentSearchQueriesUiState = RecentSearchQueriesUiState.Loading, - searchResultUiState: SearchResultUiState = SearchResultUiState.Loading, ) { TrackScreenViewEvent(screenName = "Search") Column(modifier = modifier) { @@ -177,8 +177,8 @@ internal fun SearchScreen( is SearchResultUiState.Success -> { if (searchResultUiState.isEmpty()) { EmptySearchResultBody( - onInterestsClick = onInterestsClick, searchQuery = searchQuery, + onInterestsClick = onInterestsClick, ) if (recentSearchesUiState is RecentSearchQueriesUiState.Success) { RecentSearchesBody( @@ -192,14 +192,14 @@ internal fun SearchScreen( } } else { SearchResultBody( + searchQuery = searchQuery, topics = searchResultUiState.topics, - onFollowButtonClick = onFollowButtonClick, - onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged, - onNewsResourceViewed = onNewsResourceViewed, + newsResources = searchResultUiState.newsResources, onSearchTriggered = onSearchTriggered, onTopicClick = onTopicClick, - newsResources = searchResultUiState.newsResources, - searchQuery = searchQuery, + onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged, + onNewsResourceViewed = onNewsResourceViewed, + onFollowButtonClick = onFollowButtonClick, ) } } @@ -210,8 +210,8 @@ internal fun SearchScreen( @Composable fun EmptySearchResultBody( - onInterestsClick: () -> Unit, searchQuery: String, + onInterestsClick: () -> Unit, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -286,14 +286,14 @@ private fun SearchNotReadyBody() { @Composable private fun SearchResultBody( + searchQuery: String, topics: List, newsResources: List, - onFollowButtonClick: (String, Boolean) -> Unit, - onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, - onNewsResourceViewed: (String) -> Unit, onSearchTriggered: (String) -> Unit, onTopicClick: (String) -> Unit, - searchQuery: String = "", + onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, + onNewsResourceViewed: (String) -> Unit, + onFollowButtonClick: (String, Boolean) -> Unit, ) { val state = rememberLazyStaggeredGridState() Box( @@ -392,9 +392,9 @@ private fun SearchResultBody( @Composable private fun RecentSearchesBody( + recentSearchQueries: List, onClearRecentSearches: () -> Unit, onRecentSearchClicked: (String) -> Unit, - recentSearchQueries: List, ) { Column { Row( @@ -444,11 +444,11 @@ private fun RecentSearchesBody( @Composable private fun SearchToolbar( - modifier: Modifier = Modifier, - onBackClick: () -> Unit, + searchQuery: String, onSearchQueryChanged: (String) -> Unit, - searchQuery: String = "", onSearchTriggered: (String) -> Unit, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -473,8 +473,8 @@ private fun SearchToolbar( @OptIn(ExperimentalComposeUiApi::class) @Composable private fun SearchTextField( - onSearchQueryChanged: (String) -> Unit, searchQuery: String, + onSearchQueryChanged: (String) -> Unit, onSearchTriggered: (String) -> Unit, ) { val focusRequester = remember { FocusRequester() } @@ -556,6 +556,7 @@ private fun SearchTextField( private fun SearchToolbarPreview() { NiaTheme { SearchToolbar( + searchQuery = "", onBackClick = {}, onSearchQueryChanged = {}, onSearchTriggered = {}, From 77cbdb62a438487c9a8fa843dbe47a7f262ef90a Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 5 Mar 2024 11:33:45 +0000 Subject: [PATCH 19/23] Remove unused release variable. Fixes #1231 Change-Id: I8c03d3d2e1aee85a22d1a03b97e3bec4069e0fcb --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d674f4bec..f377e0a33 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -45,7 +45,7 @@ android { debug { applicationIdSuffix = NiaBuildType.DEBUG.applicationIdSuffix } - val release = getByName("release") { + release { isMinifyEnabled = true applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") From a96526a79b7dc01e0c9d25a90c92cac21382c164 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 19:07:43 +0000 Subject: [PATCH 20/23] Bump gradle/wrapper-validation-action from 1 to 2 Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Build.yaml | 2 +- .github/workflows/Release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 2a1c60c8d..001140a87 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index f738ae105..7de3cb11e 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@v1 + uses: gradle/wrapper-validation-action@v2 - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties From ab9033421017962c5af6d7a1dd221997439210da Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Wed, 6 Mar 2024 08:38:17 +0000 Subject: [PATCH 21/23] Disable animations of instrumented tests (#1167) https://developer.android.com/reference/tools/gradle-api/com/android/build/api/dsl/TestOptions#animationsDisabled() --- .../src/main/kotlin/AndroidApplicationConventionPlugin.kt | 2 ++ .../src/main/kotlin/AndroidFeatureConventionPlugin.kt | 1 + .../src/main/kotlin/AndroidLibraryConventionPlugin.kt | 1 + 3 files changed, 4 insertions(+) diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt index cd8bcfeb0..f4d5bb0d0 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt @@ -39,6 +39,8 @@ class AndroidApplicationConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) defaultConfig.targetSdk = 34 + @Suppress("UnstableApiUsage") + testOptions.animationsDisabled = true configureGradleManagedDevices(this) } extensions.configure { diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index b8699a05d..52c337521 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -34,6 +34,7 @@ class AndroidFeatureConventionPlugin : Plugin { testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" } + testOptions.animationsDisabled = true configureGradleManagedDevices(this) } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index d442d94ef..be5b41d07 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -40,6 +40,7 @@ class AndroidLibraryConventionPlugin : Plugin { extensions.configure { configureKotlinAndroid(this) defaultConfig.targetSdk = 34 + testOptions.animationsDisabled = true configureFlavors(this) configureGradleManagedDevices(this) // The resource prefix is derived from the module name, From abe798056e32f3f40fa3614670d6d24bf2aa71f9 Mon Sep 17 00:00:00 2001 From: Jaehwa Noh Date: Thu, 7 Mar 2024 08:29:59 +0900 Subject: [PATCH 22/23] Add @After to guarantee DB was closed. (#1240) * Add @After to guarantee DB was closed. Change-Id: Ic29b7d12125853c71ddaef1a997ec123bb2dfc8b * Fix spotless - style Co-authored-by: Simon Marquis --------- Co-authored-by: Simon Marquis --- .../nowinandroid/core/database/dao/NewsResourceDaoTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/database/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDaoTest.kt b/core/database/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDaoTest.kt index 55e817618..535ab61a7 100644 --- a/core/database/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDaoTest.kt +++ b/core/database/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/database/dao/NewsResourceDaoTest.kt @@ -27,6 +27,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant +import org.junit.After import org.junit.Before import org.junit.Test import kotlin.test.assertEquals @@ -48,6 +49,9 @@ class NewsResourceDaoTest { topicDao = db.topicDao() } + @After + fun closeDb() = db.close() + @Test fun newsResourceDao_fetches_items_by_descending_publish_date() = runTest { val newsResourceEntities = listOf( From 19f6f9e09ac387a8456ff59102b9666a7a7ac9e3 Mon Sep 17 00:00:00 2001 From: Jonathan Koren Date: Thu, 7 Mar 2024 13:22:50 -0800 Subject: [PATCH 23/23] 2 pane support in Interests screen (#1234) * Add dependency on material3 adaptive Change-Id: Ic49934112a4bdbf15a68c694fbc6b0f23de960a6 * Add InterestsListDetailScreen composable Change-Id: I27e1f6d2e0eeac781baf2b671fa51a864ea5a971 * Store selectedTopicId in InterestsViewModel Change-Id: Id93704335686f171fbf80bdb54865d0f32dc36ce * Pass detail pane composable down Change-Id: I82752d8cfbb3519395f37748fb5f64b769c0c293 * Navigate to initial topic if provided Change-Id: I8998a55a29cdaf90577fa730d55c4ac2f54d6e5b * Lift LDPS up to app module Change-Id: Ibc6e8e598cd0cb62f804f11b2e48d8ae3a81df85 * Fix some navigation behavior Change-Id: Ib6c16aff692b9ce997747a30f2863303cc82fd8b * Navigate to initial topic if provided Change-Id: Iaafe4f876655d51243d7b99be985e9440fe2d4ed * Remove dependency in interests feature module Change-Id: Id517c95e11f93e1c7e17d749a7af0cfdf6085a1f * Hide back arrow when the topics list is visible Change-Id: I8901c3f79b11d35568f0ae779f97fab90e574aa8 * Update interests tests Change-Id: Ie5daf55985fdb53570397cb652abe31bad78f5cd * Highlight selected topic when displaying 2 panes Change-Id: Ifef9fb599f828f58390374b11eacc8be6c280415 * update dependency baselines Change-Id: I90dc21df3337865f4c5368634d3d45fcb0eccc00 * run spotless apply Change-Id: Ib5fb1b7fc26a62bd5e271c2a3721f1c13173f7f8 * Convert isListPaneHidden to isListPaneVisible Change-Id: I6e54f710df7db5ed6f3ec1cb284bc29f2763a657 * Set semantics for selected state Change-Id: I31f27d5036d07c9607909c09ac52a72391f899ca * Use scaffold roles when determining visibility Change-Id: Ib5fe236f182a5eeab20b61692a1cd53c17b68648 * Update multipleBackStackInterests test Change-Id: I1e372f7989817151a6765205291b13b561187fa8 --- .../dependencies/releaseRuntimeClasspath.txt | 66 ++++----- app/build.gradle.kts | 10 +- .../prodReleaseRuntimeClasspath.txt | 71 ++++----- .../apps/nowinandroid/ui/NavigationTest.kt | 10 +- .../nowinandroid/navigation/NiaNavHost.kt | 21 +-- .../apps/nowinandroid/ui/NiaAppState.kt | 4 +- .../interests2pane/Interests2PaneViewModel.kt | 35 +++++ .../InterestsListDetailScreen.kt | 136 ++++++++++++++++++ feature/interests/build.gradle.kts | 2 +- .../interests/InterestsScreenTest.kt | 5 +- .../feature/interests/InterestsItem.kt | 30 +++- .../feature/interests/InterestsScreen.kt | 15 +- .../feature/interests/InterestsViewModel.kt | 29 ++-- .../feature/interests/TabContent.kt | 4 + .../navigation/InterestsNavigation.kt | 37 +++-- .../interests/InterestsViewModelTest.kt | 13 +- .../feature/topic/TopicScreenTest.kt | 4 + .../nowinandroid/feature/topic/TopicScreen.kt | 30 ++-- .../topic/navigation/TopicNavigation.kt | 16 ++- gradle/libs.versions.toml | 4 +- 20 files changed, 407 insertions(+), 135 deletions(-) create mode 100644 app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt create mode 100644 app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 5a619eb1b..ca4bf99a3 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -12,41 +12,41 @@ androidx.browser:browser:1.6.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.1 -androidx.compose.animation:animation-core-android:1.6.1 -androidx.compose.animation:animation-core:1.6.1 -androidx.compose.animation:animation:1.6.1 -androidx.compose.foundation:foundation-android:1.6.1 -androidx.compose.foundation:foundation-layout-android:1.6.1 -androidx.compose.foundation:foundation-layout:1.6.1 -androidx.compose.foundation:foundation:1.6.1 +androidx.compose.animation:animation-android:1.6.2 +androidx.compose.animation:animation-core-android:1.6.2 +androidx.compose.animation:animation-core:1.6.2 +androidx.compose.animation:animation:1.6.2 +androidx.compose.foundation:foundation-android:1.6.2 +androidx.compose.foundation:foundation-layout-android:1.6.2 +androidx.compose.foundation:foundation-layout:1.6.2 +androidx.compose.foundation:foundation:1.6.2 androidx.compose.material3:material3-android:1.2.0 androidx.compose.material3:material3:1.2.0 -androidx.compose.material:material-icons-core-android:1.6.1 -androidx.compose.material:material-icons-core:1.6.1 -androidx.compose.material:material-icons-extended-android:1.6.1 -androidx.compose.material:material-icons-extended:1.6.1 -androidx.compose.material:material-ripple-android:1.6.1 -androidx.compose.material:material-ripple:1.6.1 -androidx.compose.runtime:runtime-android:1.6.1 -androidx.compose.runtime:runtime-saveable-android:1.6.1 -androidx.compose.runtime:runtime-saveable:1.6.1 -androidx.compose.runtime:runtime:1.6.1 -androidx.compose.ui:ui-android:1.6.1 -androidx.compose.ui:ui-geometry-android:1.6.1 -androidx.compose.ui:ui-geometry:1.6.1 -androidx.compose.ui:ui-graphics-android:1.6.1 -androidx.compose.ui:ui-graphics:1.6.1 -androidx.compose.ui:ui-text-android:1.6.1 -androidx.compose.ui:ui-text:1.6.1 -androidx.compose.ui:ui-tooling-preview-android:1.6.1 -androidx.compose.ui:ui-tooling-preview:1.6.1 -androidx.compose.ui:ui-unit-android:1.6.1 -androidx.compose.ui:ui-unit:1.6.1 -androidx.compose.ui:ui-util-android:1.6.1 -androidx.compose.ui:ui-util:1.6.1 -androidx.compose.ui:ui:1.6.1 -androidx.compose:compose-bom:2024.02.00 +androidx.compose.material:material-icons-core-android:1.6.2 +androidx.compose.material:material-icons-core:1.6.2 +androidx.compose.material:material-icons-extended-android:1.6.2 +androidx.compose.material:material-icons-extended:1.6.2 +androidx.compose.material:material-ripple-android:1.6.2 +androidx.compose.material:material-ripple:1.6.2 +androidx.compose.runtime:runtime-android:1.6.2 +androidx.compose.runtime:runtime-saveable-android:1.6.2 +androidx.compose.runtime:runtime-saveable:1.6.2 +androidx.compose.runtime:runtime:1.6.2 +androidx.compose.ui:ui-android:1.6.2 +androidx.compose.ui:ui-geometry-android:1.6.2 +androidx.compose.ui:ui-geometry:1.6.2 +androidx.compose.ui:ui-graphics-android:1.6.2 +androidx.compose.ui:ui-graphics:1.6.2 +androidx.compose.ui:ui-text-android:1.6.2 +androidx.compose.ui:ui-text:1.6.2 +androidx.compose.ui:ui-tooling-preview-android:1.6.2 +androidx.compose.ui:ui-tooling-preview:1.6.2 +androidx.compose.ui:ui-unit-android:1.6.2 +androidx.compose.ui:ui-unit:1.6.2 +androidx.compose.ui:ui-util-android:1.6.2 +androidx.compose.ui:ui-util:1.6.2 +androidx.compose.ui:ui:1.6.2 +androidx.compose:compose-bom:2024.02.01 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.12.0 androidx.core:core:1.12.0 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7cd78f75..12ac3ded3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,17 +89,21 @@ dependencies { implementation(projects.sync.work) implementation(libs.androidx.activity.compose) + implementation(libs.androidx.compose.material3.adaptive) + implementation(libs.androidx.compose.material3.windowSizeClass) + implementation(libs.androidx.compose.runtime.tracing) implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.tracing.ktx) + implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.lifecycle.runtimeCompose) - implementation(libs.androidx.compose.runtime.tracing) - implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.profileinstaller) + implementation(libs.androidx.tracing.ktx) implementation(libs.kotlinx.coroutines.guava) implementation(libs.coil.kt) + ksp(libs.hilt.compiler) + debugImplementation(libs.androidx.compose.ui.testManifest) debugImplementation(projects.uiTestHiltManifest) diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index f354c6fee..b9da05522 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -13,44 +13,46 @@ androidx.browser:browser:1.6.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.1 -androidx.compose.animation:animation-core-android:1.6.1 -androidx.compose.animation:animation-core:1.6.1 -androidx.compose.animation:animation:1.6.1 -androidx.compose.foundation:foundation-android:1.6.1 -androidx.compose.foundation:foundation-layout-android:1.6.1 -androidx.compose.foundation:foundation-layout:1.6.1 -androidx.compose.foundation:foundation:1.6.1 +androidx.compose.animation:animation-android:1.6.2 +androidx.compose.animation:animation-core-android:1.6.2 +androidx.compose.animation:animation-core:1.6.2 +androidx.compose.animation:animation:1.6.2 +androidx.compose.foundation:foundation-android:1.6.2 +androidx.compose.foundation:foundation-layout-android:1.6.2 +androidx.compose.foundation:foundation-layout:1.6.2 +androidx.compose.foundation:foundation:1.6.2 +androidx.compose.material3:material3-adaptive-android:1.0.0-alpha06 +androidx.compose.material3:material3-adaptive:1.0.0-alpha06 androidx.compose.material3:material3-android:1.2.0 androidx.compose.material3:material3-window-size-class-android:1.2.0 androidx.compose.material3:material3-window-size-class:1.2.0 androidx.compose.material3:material3:1.2.0 -androidx.compose.material:material-icons-core-android:1.6.1 -androidx.compose.material:material-icons-core:1.6.1 -androidx.compose.material:material-icons-extended-android:1.6.1 -androidx.compose.material:material-icons-extended:1.6.1 -androidx.compose.material:material-ripple-android:1.6.1 -androidx.compose.material:material-ripple:1.6.1 -androidx.compose.runtime:runtime-android:1.6.1 -androidx.compose.runtime:runtime-saveable-android:1.6.1 -androidx.compose.runtime:runtime-saveable:1.6.1 +androidx.compose.material:material-icons-core-android:1.6.2 +androidx.compose.material:material-icons-core:1.6.2 +androidx.compose.material:material-icons-extended-android:1.6.2 +androidx.compose.material:material-icons-extended:1.6.2 +androidx.compose.material:material-ripple-android:1.6.2 +androidx.compose.material:material-ripple:1.6.2 +androidx.compose.runtime:runtime-android:1.6.2 +androidx.compose.runtime:runtime-saveable-android:1.6.2 +androidx.compose.runtime:runtime-saveable:1.6.2 androidx.compose.runtime:runtime-tracing:1.0.0-beta01 -androidx.compose.runtime:runtime:1.6.1 -androidx.compose.ui:ui-android:1.6.1 -androidx.compose.ui:ui-geometry-android:1.6.1 -androidx.compose.ui:ui-geometry:1.6.1 -androidx.compose.ui:ui-graphics-android:1.6.1 -androidx.compose.ui:ui-graphics:1.6.1 -androidx.compose.ui:ui-text-android:1.6.1 -androidx.compose.ui:ui-text:1.6.1 -androidx.compose.ui:ui-tooling-preview-android:1.6.1 -androidx.compose.ui:ui-tooling-preview:1.6.1 -androidx.compose.ui:ui-unit-android:1.6.1 -androidx.compose.ui:ui-unit:1.6.1 -androidx.compose.ui:ui-util-android:1.6.1 -androidx.compose.ui:ui-util:1.6.1 -androidx.compose.ui:ui:1.6.1 -androidx.compose:compose-bom:2024.02.00 +androidx.compose.runtime:runtime:1.6.2 +androidx.compose.ui:ui-android:1.6.2 +androidx.compose.ui:ui-geometry-android:1.6.2 +androidx.compose.ui:ui-geometry:1.6.2 +androidx.compose.ui:ui-graphics-android:1.6.2 +androidx.compose.ui:ui-graphics:1.6.2 +androidx.compose.ui:ui-text-android:1.6.2 +androidx.compose.ui:ui-text:1.6.2 +androidx.compose.ui:ui-tooling-preview-android:1.6.2 +androidx.compose.ui:ui-tooling-preview:1.6.2 +androidx.compose.ui:ui-unit-android:1.6.2 +androidx.compose.ui:ui-unit:1.6.2 +androidx.compose.ui:ui-util-android:1.6.2 +androidx.compose.ui:ui-util:1.6.2 +androidx.compose.ui:ui:1.6.2 +androidx.compose:compose-bom:2024.02.01 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.12.0 androidx.core:core-splashscreen:1.0.1 @@ -116,7 +118,8 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -androidx.window:window:1.0.0 +androidx.window.extensions.core:core:1.0.0 +androidx.window:window:1.2.0 androidx.work:work-runtime-ktx:2.9.0 androidx.work:work-runtime:2.9.0 com.caverock:androidsvg-aar:1.4 diff --git a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt index 13f11c3b9..c9cc64120 100644 --- a/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt +++ b/app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt @@ -274,10 +274,10 @@ class NavigationTest { // Select the last topic val topic = runBlocking { - topicsRepository.getTopics().first().sortedBy(Topic::name).last().name + topicsRepository.getTopics().first().sortedBy(Topic::name).last() } - onNodeWithTag("interests:topics").performScrollToNode(hasText(topic)) - onNodeWithText(topic).performClick() + onNodeWithTag("interests:topics").performScrollToNode(hasText(topic.name)) + onNodeWithText(topic.name).performClick() // Switch tab onNodeWithText(forYou).performClick() @@ -285,8 +285,8 @@ class NavigationTest { // Come back to Interests onNodeWithText(interests).performClick() - // Verify we're not in the list of interests - onNodeWithTag("interests:topics").assertDoesNotExist() + // Verify the topic is still shown + onNodeWithTag("topic:${topic.id}").assertExists() } } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt index 6167b0b59..39bc03de7 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/NiaNavHost.kt @@ -22,12 +22,11 @@ import androidx.navigation.compose.NavHost import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouScreen -import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsGraph +import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen -import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic -import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS import com.google.samples.apps.nowinandroid.ui.NiaAppState +import com.google.samples.apps.nowinandroid.ui.interests2pane.interestsListDetailScreen /** * Top-level navigation graph. Navigation is organized as explained at @@ -49,24 +48,16 @@ fun NiaNavHost( startDestination = startDestination, modifier = modifier, ) { - forYouScreen(onTopicClick = navController::navigateToTopic) + forYouScreen(onTopicClick = navController::navigateToInterests) bookmarksScreen( - onTopicClick = navController::navigateToTopic, + onTopicClick = navController::navigateToInterests, onShowSnackbar = onShowSnackbar, ) searchScreen( onBackClick = navController::popBackStack, onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) }, - onTopicClick = navController::navigateToTopic, - ) - interestsGraph( - onTopicClick = navController::navigateToTopic, - nestedGraphs = { - topicScreen( - onBackClick = navController::popBackStack, - onTopicClick = navController::navigateToTopic, - ) - }, + onTopicClick = navController::navigateToInterests, ) + interestsListDetailScreen() } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt index d423adfbf..b653d8910 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt @@ -39,7 +39,7 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigat import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE import com.google.samples.apps.nowinandroid.feature.foryou.navigation.navigateToForYou import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE -import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterestsGraph +import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests import com.google.samples.apps.nowinandroid.feature.search.navigation.navigateToSearch import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKMARKS @@ -173,7 +173,7 @@ class NiaAppState( when (topLevelDestination) { FOR_YOU -> navController.navigateToForYou(topLevelNavOptions) BOOKMARKS -> navController.navigateToBookmarks(topLevelNavOptions) - INTERESTS -> navController.navigateToInterestsGraph(topLevelNavOptions) + INTERESTS -> navController.navigateToInterests(null, topLevelNavOptions) } } } 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 new file mode 100644 index 000000000..d618c2d47 --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.ui.interests2pane + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@HiltViewModel +class Interests2PaneViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, +) : ViewModel() { + val selectedTopicId: StateFlow = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null) + + 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 new file mode 100644 index 000000000..98327923f --- /dev/null +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.ui.interests2pane + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.ListDetailPaneScaffold +import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole +import androidx.compose.material3.adaptive.PaneAdaptedValue +import androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator +import androidx.compose.material3.adaptive.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute +import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG +import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE +import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic +import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen + +private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route" + +fun NavGraphBuilder.interestsListDetailScreen() { + composable( + route = INTERESTS_ROUTE, + arguments = listOf( + navArgument(TOPIC_ID_ARG) { + type = NavType.StringType + defaultValue = null + nullable = true + }, + ), + ) { + InterestsListDetailScreen() + } +} + +@Composable +internal fun InterestsListDetailScreen( + viewModel: Interests2PaneViewModel = hiltViewModel(), +) { + val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle() + InterestsListDetailScreen( + selectedTopicId = selectedTopicId, + onTopicClick = viewModel::onTopicClick, + ) +} + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +internal fun InterestsListDetailScreen( + selectedTopicId: String?, + onTopicClick: (String) -> Unit, +) { + val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() + BackHandler(listDetailNavigator.canNavigateBack()) { + listDetailNavigator.navigateBack() + } + + val nestedNavController = rememberNavController() + + fun onTopicClickShowDetailPane(topicId: String) { + onTopicClick(topicId) + nestedNavController.navigateToTopic(topicId) { + popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + } + listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) + } + + ListDetailPaneScaffold( + scaffoldState = listDetailNavigator.scaffoldState, + 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, + onTopicClick = ::onTopicClickShowDetailPane, + ) + composable(route = TOPIC_ROUTE) { + Box { + Text("Placeholder") + } + } + } + }, + ) + LaunchedEffect(Unit) { + if (selectedTopicId != null) { + // Initial topic ID was provided when navigating to Interests, so show its details. + onTopicClickShowDetailPane(selectedTopicId) + } + } +} + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +private fun ThreePaneScaffoldNavigator.isListPaneVisible(): Boolean = + scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded + +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +private fun ThreePaneScaffoldNavigator.isDetailPaneVisible(): Boolean = + scaffoldState.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded diff --git a/feature/interests/build.gradle.kts b/feature/interests/build.gradle.kts index f0bca9729..ee6aaf122 100644 --- a/feature/interests/build.gradle.kts +++ b/feature/interests/build.gradle.kts @@ -30,4 +30,4 @@ dependencies { testImplementation(projects.core.testing) androidTestImplementation(projects.core.testing) -} \ No newline at end of file +} diff --git a/feature/interests/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt b/feature/interests/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt index 4f9cbcc04..1584662b8 100644 --- a/feature/interests/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt +++ b/feature/interests/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt @@ -74,7 +74,10 @@ class InterestsScreenTest { fun interestsWithTopics_whenTopicsFollowed_showFollowedAndUnfollowedTopicsWithInfo() { composeTestRule.setContent { InterestsScreen( - uiState = InterestsUiState.Interests(topics = followableTopicTestData), + uiState = InterestsUiState.Interests( + topics = followableTopicTestData, + selectedTopicId = null, + ), ) } diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt index b055a3a14..6ac0340ee 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.selected import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -49,6 +50,7 @@ fun InterestsItem( modifier: Modifier = Modifier, iconModifier: Modifier = Modifier, description: String = "", + isSelected: Boolean = false, ) { ListItem( leadingContent = { @@ -83,10 +85,16 @@ fun InterestsItem( ) }, colors = ListItemDefaults.colors( - containerColor = Color.Transparent, + containerColor = if (isSelected) { + MaterialTheme.colorScheme.surfaceVariant + } else { + Color.Transparent + }, ), modifier = modifier - .semantics(mergeDescendants = true) { /* no-op */ } + .semantics(mergeDescendants = true) { + selected = isSelected + } .clickable(enabled = true, onClick = onClick), ) } @@ -179,3 +187,21 @@ private fun InterestsCardWithEmptyDescriptionPreview() { } } } + +@Preview +@Composable +private fun InterestsCardSelectedPreview() { + NiaTheme { + Surface { + InterestsItem( + name = "Compose", + description = "", + following = true, + topicImageUrl = "", + onClick = { }, + onFollowButtonClick = { }, + isSelected = true, + ) + } + } +} diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt index 5944b8631..468550878 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt @@ -35,9 +35,10 @@ import com.google.samples.apps.nowinandroid.core.ui.FollowableTopicPreviewParame import com.google.samples.apps.nowinandroid.core.ui.TrackScreenViewEvent @Composable -internal fun InterestsRoute( +fun InterestsRoute( onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, + highlightSelectedTopic: Boolean = false, viewModel: InterestsViewModel = hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() @@ -45,7 +46,11 @@ internal fun InterestsRoute( InterestsScreen( uiState = uiState, followTopic = viewModel::followTopic, - onTopicClick = onTopicClick, + onTopicClick = { + viewModel.onTopicClick(it) + onTopicClick(it) + }, + highlightSelectedTopic = highlightSelectedTopic, modifier = modifier, ) } @@ -56,6 +61,7 @@ internal fun InterestsScreen( followTopic: (String, Boolean) -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, + highlightSelectedTopic: Boolean = false, ) { Column( modifier = modifier, @@ -67,13 +73,17 @@ internal fun InterestsScreen( modifier = modifier, contentDesc = stringResource(id = R.string.feature_interests_loading), ) + is InterestsUiState.Interests -> TopicsTabContent( topics = uiState.topics, onTopicClick = onTopicClick, onFollowButtonClick = followTopic, + selectedTopicId = uiState.selectedTopicId, + highlightSelectedTopic = highlightSelectedTopic, modifier = modifier, ) + is InterestsUiState.Empty -> InterestsEmptyScreen() } } @@ -95,6 +105,7 @@ fun InterestsScreenPopulated( NiaBackground { InterestsScreen( uiState = InterestsUiState.Interests( + selectedTopicId = null, topics = followableTopics, ), followTopic = { _, _ -> }, diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt index 6d905a6d5..b369ac5ab 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/InterestsViewModel.kt @@ -16,46 +16,57 @@ package com.google.samples.apps.nowinandroid.feature.interests +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase import com.google.samples.apps.nowinandroid.core.domain.TopicSortField import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class InterestsViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, val userDataRepository: UserDataRepository, getFollowableTopics: GetFollowableTopicsUseCase, ) : ViewModel() { - val uiState: StateFlow = - getFollowableTopics(sortBy = TopicSortField.NAME).map( - InterestsUiState::Interests, - ).stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(5_000), - initialValue = InterestsUiState.Loading, - ) + val selectedTopicId: StateFlow = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null) + + val uiState: StateFlow = combine( + selectedTopicId, + getFollowableTopics(sortBy = TopicSortField.NAME), + InterestsUiState::Interests, + ).stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = InterestsUiState.Loading, + ) fun followTopic(followedTopicId: String, followed: Boolean) { viewModelScope.launch { userDataRepository.setTopicIdFollowed(followedTopicId, followed) } } + + fun onTopicClick(topicId: String?) { + savedStateHandle[TOPIC_ID_ARG] = topicId + } } sealed interface InterestsUiState { data object Loading : InterestsUiState data class Interests( + val selectedTopicId: String?, val topics: List, ) : InterestsUiState diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt index d865f5e1a..4a48645c5 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/TabContent.kt @@ -47,6 +47,8 @@ fun TopicsTabContent( onFollowButtonClick: (String, Boolean) -> Unit, modifier: Modifier = Modifier, withBottomSpacer: Boolean = true, + selectedTopicId: String? = null, + highlightSelectedTopic: Boolean = false, ) { Box( modifier = modifier @@ -63,6 +65,7 @@ fun TopicsTabContent( topics.forEach { followableTopic -> val topicId = followableTopic.topic.id item(key = topicId) { + val isSelected = highlightSelectedTopic && topicId == selectedTopicId InterestsItem( name = followableTopic.topic.name, following = followableTopic.isFollowed, @@ -70,6 +73,7 @@ fun TopicsTabContent( topicImageUrl = followableTopic.topic.imageUrl, onClick = { onTopicClick(topicId) }, onFollowButtonClick = { onFollowButtonClick(topicId, it) }, + isSelected = isSelected, ) } } diff --git a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt index 2ad7c560b..8a0f2d130 100644 --- a/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt +++ b/feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt @@ -19,26 +19,37 @@ package com.google.samples.apps.nowinandroid.feature.interests.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions +import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.navigation +import androidx.navigation.navArgument import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute -private const val INTERESTS_GRAPH_ROUTE_PATTERN = "interests_graph" -const val INTERESTS_ROUTE = "interests_route" +const val TOPIC_ID_ARG = "topicId" +const val INTERESTS_ROUTE_BASE = "interests_route" +const val INTERESTS_ROUTE = "$INTERESTS_ROUTE_BASE?$TOPIC_ID_ARG={$TOPIC_ID_ARG}" -fun NavController.navigateToInterestsGraph(navOptions: NavOptions) = navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions) +fun NavController.navigateToInterests(topicId: String? = null, navOptions: NavOptions? = null) { + val route = if (topicId != null) { + "${INTERESTS_ROUTE_BASE}?${TOPIC_ID_ARG}=$topicId" + } else { + INTERESTS_ROUTE_BASE + } + navigate(route, navOptions) +} -fun NavGraphBuilder.interestsGraph( +fun NavGraphBuilder.interestsScreen( onTopicClick: (String) -> Unit, - nestedGraphs: NavGraphBuilder.() -> Unit, ) { - navigation( - route = INTERESTS_GRAPH_ROUTE_PATTERN, - startDestination = INTERESTS_ROUTE, + composable( + route = INTERESTS_ROUTE, + arguments = listOf( + navArgument(TOPIC_ID_ARG) { + defaultValue = null + nullable = true + type = NavType.StringType + }, + ), ) { - composable(route = INTERESTS_ROUTE) { - InterestsRoute(onTopicClick) - } - nestedGraphs() + InterestsRoute(onTopicClick = onTopicClick) } } diff --git a/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt b/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt index c46cb7780..63d3c49b7 100644 --- a/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt +++ b/feature/interests/src/test/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsViewModelTest.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.interests +import androidx.lifecycle.SavedStateHandle import com.google.samples.apps.nowinandroid.core.domain.GetFollowableTopicsUseCase import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.Topic @@ -24,6 +25,7 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserData import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule import com.google.samples.apps.nowinandroid.feature.interests.InterestsUiState import com.google.samples.apps.nowinandroid.feature.interests.InterestsViewModel +import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -53,6 +55,7 @@ class InterestsViewModelTest { @Before fun setup() { viewModel = InterestsViewModel( + savedStateHandle = SavedStateHandle(mapOf(TOPIC_ID_ARG to testInputTopics[0].topic.id)), userDataRepository = userDataRepository, getFollowableTopics = getFollowableTopicsUseCase, ) @@ -93,7 +96,10 @@ class InterestsViewModelTest { ) assertEquals( - InterestsUiState.Interests(topics = testOutputTopics), + InterestsUiState.Interests( + topics = testOutputTopics, + selectedTopicId = testInputTopics[0].topic.id, + ), viewModel.uiState.value, ) @@ -123,7 +129,10 @@ class InterestsViewModelTest { ) assertEquals( - InterestsUiState.Interests(topics = testInputTopics), + InterestsUiState.Interests( + topics = testInputTopics, + selectedTopicId = testInputTopics[0].topic.id, + ), viewModel.uiState.value, ) diff --git a/feature/topic/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt b/feature/topic/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt index b64e397ea..2b87baf9e 100644 --- a/feature/topic/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt +++ b/feature/topic/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt @@ -55,6 +55,7 @@ class TopicScreenTest { TopicScreen( topicUiState = TopicUiState.Loading, newsUiState = NewsUiState.Loading, + showBackButton = true, onBackClick = {}, onFollowClick = {}, onTopicClick = {}, @@ -75,6 +76,7 @@ class TopicScreenTest { TopicScreen( topicUiState = TopicUiState.Success(testTopic), newsUiState = NewsUiState.Loading, + showBackButton = true, onBackClick = {}, onFollowClick = {}, onTopicClick = {}, @@ -100,6 +102,7 @@ class TopicScreenTest { TopicScreen( topicUiState = TopicUiState.Loading, newsUiState = NewsUiState.Success(userNewsResourcesTestData), + showBackButton = true, onBackClick = {}, onFollowClick = {}, onTopicClick = {}, @@ -123,6 +126,7 @@ class TopicScreenTest { newsUiState = NewsUiState.Success( userNewsResourcesTestData, ), + showBackButton = true, onBackClick = {}, onFollowClick = {}, onTopicClick = {}, diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index 3f3862c2a..4402cecd4 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsTopHeight @@ -44,6 +45,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -70,6 +72,7 @@ import com.google.samples.apps.nowinandroid.feature.topic.R.string @Composable internal fun TopicRoute( + showBackButton: Boolean, onBackClick: () -> Unit, onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, @@ -82,7 +85,8 @@ internal fun TopicRoute( TopicScreen( topicUiState = topicUiState, newsUiState = newsUiState, - modifier = modifier, + modifier = modifier.testTag("topic:${viewModel.topicId}"), + showBackButton = showBackButton, onBackClick = onBackClick, onFollowClick = viewModel::followTopicToggle, onBookmarkChanged = viewModel::bookmarkNews, @@ -96,6 +100,7 @@ internal fun TopicRoute( internal fun TopicScreen( topicUiState: TopicUiState, newsUiState: NewsUiState, + showBackButton: Boolean, onBackClick: () -> Unit, onFollowClick: (Boolean) -> Unit, onTopicClick: (String) -> Unit, @@ -127,6 +132,7 @@ internal fun TopicScreen( is TopicUiState.Success -> { item { TopicToolbar( + showBackButton = showBackButton, onBackClick = onBackClick, onFollowClick = onFollowClick, uiState = topicUiState.followableTopic, @@ -270,6 +276,7 @@ private fun TopicBodyPreview() { private fun TopicToolbar( uiState: FollowableTopic, modifier: Modifier = Modifier, + showBackButton: Boolean = true, onBackClick: () -> Unit = {}, onFollowClick: (Boolean) -> Unit = {}, ) { @@ -280,13 +287,18 @@ private fun TopicToolbar( .fillMaxWidth() .padding(bottom = 32.dp), ) { - IconButton(onClick = { onBackClick() }) { - Icon( - imageVector = NiaIcons.ArrowBack, - contentDescription = stringResource( - id = com.google.samples.apps.nowinandroid.core.ui.R.string.core_ui_back, - ), - ) + if (showBackButton) { + IconButton(onClick = { onBackClick() }) { + Icon( + imageVector = NiaIcons.ArrowBack, + contentDescription = stringResource( + id = com.google.samples.apps.nowinandroid.core.ui.R.string.core_ui_back, + ), + ) + } + } else { + // Keeps the NiaFilterChip aligned to the end of the Row. + Spacer(modifier = Modifier.width(1.dp)) } val selected = uiState.isFollowed NiaFilterChip( @@ -314,6 +326,7 @@ fun TopicScreenPopulated( TopicScreen( topicUiState = TopicUiState.Success(userNewsResources[0].followableTopics[0]), newsUiState = NewsUiState.Success(userNewsResources), + showBackButton = true, onBackClick = {}, onFollowClick = {}, onBookmarkChanged = { _, _ -> }, @@ -332,6 +345,7 @@ fun TopicScreenLoading() { TopicScreen( topicUiState = TopicUiState.Loading, newsUiState = NewsUiState.Loading, + showBackButton = true, onBackClick = {}, onFollowClick = {}, onBookmarkChanged = { _, _ -> }, 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 bba46c5ab..41804b634 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 @@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptionsBuilder import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument @@ -32,20 +33,23 @@ private val URL_CHARACTER_ENCODING = UTF_8.name() @VisibleForTesting internal const val TOPIC_ID_ARG = "topicId" +const val TOPIC_ROUTE = "topic_route" internal class TopicArgs(val topicId: String) { constructor(savedStateHandle: SavedStateHandle) : this(URLDecoder.decode(checkNotNull(savedStateHandle[TOPIC_ID_ARG]), URL_CHARACTER_ENCODING)) } -fun NavController.navigateToTopic(topicId: String) { +fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) { val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) - navigate("topic_route/$encodedId") { - launchSingleTop = true + val newRoute = "$TOPIC_ROUTE/$encodedId" + navigate(newRoute) { + navOptions() } } fun NavGraphBuilder.topicScreen( + showBackButton: Boolean, onBackClick: () -> Unit, onTopicClick: (String) -> Unit, ) { @@ -55,6 +59,10 @@ fun NavGraphBuilder.topicScreen( navArgument(TOPIC_ID_ARG) { type = NavType.StringType }, ), ) { - TopicRoute(onBackClick = onBackClick, onTopicClick = onTopicClick) + TopicRoute( + showBackButton = showBackButton, + onBackClick = onBackClick, + onTopicClick = onTopicClick, + ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7b1318c7a..481494a48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,8 +7,9 @@ androidTools = "31.3.0" androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" -androidxComposeBom = "2024.02.00" +androidxComposeBom = "2024.02.01" androidxComposeCompiler = "1.5.8" +androidxComposeMaterial3Adaptive = "1.0.0-alpha06" androidxComposeRuntimeTracing = "1.0.0-beta01" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" @@ -70,6 +71,7 @@ androidx-compose-foundation = { group = "androidx.compose.foundation", name = "f androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-material3-adaptive = { group = "androidx.compose.material3", name = "material3-adaptive", version.ref = "androidxComposeMaterial3Adaptive" } androidx-compose-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" }