diff --git a/.editorconfig b/.editorconfig index 7be3f8784..6c8c930bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,3 +5,13 @@ ij_kotlin_allow_trailing_comma=true ij_kotlin_allow_trailing_comma_on_call_site=true ktlint_function_naming_ignore_when_annotated_with=Composable, Test +ktlint_standard_backing-property-naming = disabled +ktlint_standard_binary-expression-wrapping = disabled +ktlint_standard_chain-method-continuation = disabled +ktlint_standard_class-signature = disabled +ktlint_standard_condition-wrapping = disabled +ktlint_standard_function-expression-body = disabled +ktlint_standard_function-literal = disabled +ktlint_standard_function-type-modifier-spacing = disabled +ktlint_standard_multiline-loop = disabled +ktlint_standard_function-signature = disabled diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index c67d3b3d8..ea3ccb653 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -93,7 +93,9 @@ jobs: continue-on-error: false if: steps.screenshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository run: | - echo "::error::Screenshot tests failed, please create a PR in your fork first." && exit 1 + echo "::error::Screenshot tests failed, please create a PR in your fork first." + echo "Your fork's CI will take screenshots for your fork." + exit 1 # Runs if previous job failed - name: Generate new screenshots if verification failed and it's a PR @@ -166,7 +168,7 @@ jobs: timeout-minutes: 55 strategy: matrix: - api-level: [26, 30] + api-level: [26, 34] steps: - name: Delete unnecessary tools 🔧 @@ -235,7 +237,7 @@ jobs: - name: Display local test coverage (only API 30) if: matrix.api-level == 30 id: jacoco - uses: madrapps/jacoco-report@v1.7.0 + uses: madrapps/jacoco-report@v1.7.1 with: title: Combined test coverage report min-coverage-overall: 40 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1e4361008..5434df8c2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -123,7 +123,6 @@ dependencies { testDemoImplementation(libs.roborazzi) testDemoImplementation(projects.core.screenshotTesting) - androidTestImplementation(kotlin("test")) androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.dataTest) androidTestImplementation(projects.core.datastoreTest) @@ -131,6 +130,7 @@ dependencies { androidTestImplementation(libs.androidx.navigation.testing) androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(libs.hilt.android.testing) + androidTestImplementation(libs.kotlin.test) baselineProfile(projects.benchmarks) } diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 49ab75cec..599c4ff64 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -89,38 +89,38 @@ androidx.hilt:hilt-navigation:1.2.0 androidx.hilt:hilt-work:1.2.0 androidx.interpolator:interpolator:1.0.0 androidx.legacy:legacy-support-core-utils:1.0.0 -androidx.lifecycle:lifecycle-common-java8:2.8.6 -androidx.lifecycle:lifecycle-common-jvm:2.8.6 -androidx.lifecycle:lifecycle-common:2.8.6 -androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.6 -androidx.lifecycle:lifecycle-livedata-core:2.8.6 -androidx.lifecycle:lifecycle-livedata:2.8.6 -androidx.lifecycle:lifecycle-process:2.8.6 -androidx.lifecycle:lifecycle-runtime-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-compose-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-compose:2.8.6 -androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.6 -androidx.lifecycle:lifecycle-runtime-ktx:2.8.6 -androidx.lifecycle:lifecycle-runtime:2.8.6 -androidx.lifecycle:lifecycle-service:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-android:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6 -androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6 -androidx.lifecycle:lifecycle-viewmodel:2.8.6 +androidx.lifecycle:lifecycle-common-java8:2.8.7 +androidx.lifecycle:lifecycle-common-jvm:2.8.7 +androidx.lifecycle:lifecycle-common:2.8.7 +androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7 +androidx.lifecycle:lifecycle-livedata-core:2.8.7 +androidx.lifecycle:lifecycle-livedata:2.8.7 +androidx.lifecycle:lifecycle-process:2.8.7 +androidx.lifecycle:lifecycle-runtime-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-compose:2.8.7 +androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7 +androidx.lifecycle:lifecycle-runtime-ktx:2.8.7 +androidx.lifecycle:lifecycle-runtime:2.8.7 +androidx.lifecycle:lifecycle-service:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-android:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7 +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7 +androidx.lifecycle:lifecycle-viewmodel:2.8.7 androidx.loader:loader:1.0.0 androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 androidx.metrics:metrics-performance:1.0.0-beta01 -androidx.navigation:navigation-common-ktx:2.8.0 -androidx.navigation:navigation-common:2.8.0 -androidx.navigation:navigation-compose:2.8.0 -androidx.navigation:navigation-runtime-ktx:2.8.0 -androidx.navigation:navigation-runtime:2.8.0 +androidx.navigation:navigation-common-ktx:2.8.4 +androidx.navigation:navigation-common:2.8.4 +androidx.navigation:navigation-compose:2.8.4 +androidx.navigation:navigation-runtime-ktx:2.8.4 +androidx.navigation:navigation-runtime:2.8.4 androidx.print:print:1.0.0 androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 -androidx.profileinstaller:profileinstaller:1.3.1 +androidx.profileinstaller:profileinstaller:1.4.1 androidx.resourceinspection:resourceinspection-annotation:1.0.1 androidx.room:room-common:2.6.1 androidx.room:room-ktx:2.6.1 @@ -141,11 +141,11 @@ androidx.window.extensions.core:core:1.0.0 androidx.window:window-core-android:1.3.0 androidx.window:window-core:1.3.0 androidx.window:window:1.3.0 -androidx.work:work-runtime-ktx:2.9.0 -androidx.work:work-runtime:2.9.0 +androidx.work:work-runtime-ktx:2.9.1 +androidx.work:work-runtime:2.9.1 com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.32.0 -com.google.accompanist:accompanist-permissions:0.34.0 +com.google.accompanist:accompanist-permissions:0.36.0 com.google.android.datatransport:transport-api:3.2.0 com.google.android.datatransport:transport-backend-cct:3.3.0 com.google.android.datatransport:transport-runtime:3.3.0 @@ -153,12 +153,12 @@ com.google.android.gms:play-services-ads-identifier:18.0.0 com.google.android.gms:play-services-base:18.5.0 com.google.android.gms:play-services-basement:18.4.0 com.google.android.gms:play-services-cloud-messaging:17.2.0 -com.google.android.gms:play-services-measurement-api:22.1.0 -com.google.android.gms:play-services-measurement-base:22.1.0 -com.google.android.gms:play-services-measurement-impl:22.1.0 -com.google.android.gms:play-services-measurement-sdk-api:22.1.0 -com.google.android.gms:play-services-measurement-sdk:22.1.0 -com.google.android.gms:play-services-measurement:22.1.0 +com.google.android.gms:play-services-measurement-api:22.1.2 +com.google.android.gms:play-services-measurement-base:22.1.2 +com.google.android.gms:play-services-measurement-impl:22.1.2 +com.google.android.gms:play-services-measurement-sdk-api:22.1.2 +com.google.android.gms:play-services-measurement-sdk:22.1.2 +com.google.android.gms:play-services-measurement:22.1.2 com.google.android.gms:play-services-oss-licenses:17.1.0 com.google.android.gms:play-services-stats:17.0.2 com.google.android.gms:play-services-tasks:18.2.0 @@ -169,15 +169,15 @@ com.google.dagger:hilt-android:2.52 com.google.dagger:hilt-core:2.52 com.google.errorprone:error_prone_annotations:2.26.0 com.google.firebase:firebase-abt:21.1.1 -com.google.firebase:firebase-analytics:22.1.0 +com.google.firebase:firebase-analytics:22.1.2 com.google.firebase:firebase-annotations:16.2.0 -com.google.firebase:firebase-bom:33.3.0 +com.google.firebase:firebase-bom:33.7.0 com.google.firebase:firebase-common-ktx:21.0.0 com.google.firebase:firebase-common:21.0.0 com.google.firebase:firebase-components:18.0.0 com.google.firebase:firebase-config-interop:16.0.1 -com.google.firebase:firebase-config:22.0.0 -com.google.firebase:firebase-crashlytics:19.1.0 +com.google.firebase:firebase-config:22.0.1 +com.google.firebase:firebase-crashlytics:19.3.0 com.google.firebase:firebase-datatransport:19.0.0 com.google.firebase:firebase-encoders-json:18.0.1 com.google.firebase:firebase-encoders-proto:16.0.0 @@ -186,16 +186,15 @@ com.google.firebase:firebase-iid-interop:17.1.0 com.google.firebase:firebase-installations-interop:17.2.0 com.google.firebase:firebase-installations:18.0.0 com.google.firebase:firebase-measurement-connector:20.0.1 -com.google.firebase:firebase-messaging:24.0.1 -com.google.firebase:firebase-perf:21.0.1 -com.google.firebase:firebase-sessions:2.0.4 -com.google.firebase:protolite-well-known-types:18.0.0 +com.google.firebase:firebase-messaging:24.1.0 +com.google.firebase:firebase-perf:21.0.3 +com.google.firebase:firebase-sessions:2.0.7 com.google.guava:failureaccess:1.0.1 com.google.guava:guava:31.1-android com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava com.google.j2objc:j2objc-annotations:1.3 -com.google.protobuf:protobuf-javalite:4.26.1 -com.google.protobuf:protobuf-kotlin-lite:4.26.1 +com.google.protobuf:protobuf-javalite:4.28.2 +com.google.protobuf:protobuf-kotlin-lite:4.28.2 com.squareup.okhttp3:logging-interceptor:4.12.0 com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.9.0 diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index 640b22e83..f27b90cbe 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -37,7 +37,6 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult.ActionPerformed import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -76,7 +75,6 @@ import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import kotlin.reflect.KClass import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR -@OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun NiaApp( appState: NiaAppState, @@ -126,7 +124,6 @@ fun NiaApp( @OptIn( ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, - ExperimentalMaterial3AdaptiveApi::class, ) internal fun NiaApp( appState: NiaAppState, 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 249f07590..7c892c854 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 @@ -18,6 +18,8 @@ package com.google.samples.apps.nowinandroid.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.navigation.NavController @@ -25,7 +27,6 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navOptions import androidx.tracing.trace @@ -83,9 +84,21 @@ class NiaAppState( userNewsResourceRepository: UserNewsResourceRepository, timeZoneMonitor: TimeZoneMonitor, ) { + private val previousDestination = mutableStateOf(null) + val currentDestination: NavDestination? - @Composable get() = navController - .currentBackStackEntryAsState().value?.destination + @Composable get() { + // Collect the currentBackStackEntryFlow as a state + val currentEntry = navController.currentBackStackEntryFlow + .collectAsState(initial = null) + + // Fallback to previousDestination if currentEntry is null + return currentEntry.value?.destination.also { destination -> + if (destination != null) { + previousDestination.value = destination + } + } ?: previousDestination.value + } val currentTopLevelDestination: TopLevelDestination? @Composable get() { diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index e84b96b73..9c9488fde 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -16,7 +16,6 @@ package com.google.samples.apps.nowinandroid.ui -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider @@ -113,7 +112,6 @@ class NiaAppScreenSizesScreenshotTests { TimeZone.setDefault(TimeZone.getTimeZone("UTC")) } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testNiaAppScreenshotWithSize(width: Dp, height: Dp, screenshotName: String) { composeTestRule.setContent { CompositionLocalProvider( diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt index 2ef0d3e4f..78f568e03 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt @@ -34,7 +34,6 @@ import androidx.compose.foundation.layout.windowInsetsStartWidth import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.material3.SnackbarDuration.Indefinite import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.Composable @@ -209,7 +208,6 @@ class SnackbarInsetsScreenshotTests { } } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testSnackbarScreenshotWithSize( snackbarHostState: SnackbarHostState, width: Dp, diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt index fe2e98452..b9b1047c1 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt @@ -19,7 +19,6 @@ package com.google.samples.apps.nowinandroid.ui import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.material3.SnackbarDuration.Indefinite import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.Posture import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider @@ -182,7 +181,6 @@ class SnackbarScreenshotTests { } } - @OptIn(ExperimentalMaterial3AdaptiveApi::class) private fun testSnackbarScreenshotWithSize( snackbarHostState: SnackbarHostState, width: Dp, diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt index 422592b8a..a5e001bd2 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -21,6 +21,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.exclude class AndroidApplicationFirebaseConventionPlugin : Plugin { override fun apply(target: Project) { @@ -35,7 +36,16 @@ class AndroidApplicationFirebaseConventionPlugin : Plugin { val bom = libs.findLibrary("firebase-bom").get() add("implementation", platform(bom)) "implementation"(libs.findLibrary("firebase.analytics").get()) - "implementation"(libs.findLibrary("firebase.performance").get()) + "implementation"(libs.findLibrary("firebase.performance").get()) { + /* + Exclusion of protobuf / protolite dependencies is necessary as the + datastore-proto brings in protobuf dependencies. These are the source of truth + for Now in Android. + That's why the duplicate classes from below dependencies are excluded. + */ + exclude(group = "com.google.protobuf", module = "protobuf-javalite") + exclude(group = "com.google.firebase", module = "protolite-well-known-types") + } "implementation"(libs.findLibrary("firebase.crashlytics").get()) } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 71d818c0c..2a10901fb 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -53,8 +53,8 @@ class AndroidLibraryConventionPlugin : Plugin { disableUnnecessaryAndroidTests(target) } dependencies { - add("androidTestImplementation", kotlin("test")) - add("testImplementation", kotlin("test")) + add("androidTestImplementation", libs.findLibrary("kotlin.test").get()) + add("testImplementation", libs.findLibrary("kotlin.test").get()) add("implementation", libs.findLibrary("androidx.tracing.ktx").get()) } diff --git a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt index 652409db6..afe47eeee 100644 --- a/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt @@ -15,6 +15,7 @@ */ import com.google.samples.apps.nowinandroid.configureKotlinJvm +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies @@ -29,7 +30,7 @@ class JvmLibraryConventionPlugin : Plugin { } configureKotlinJvm() dependencies { - add("testImplementation", kotlin("test")) + add("testImplementation", libs.findLibrary("kotlin.test").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt index d0c26e4e6..c51dac5c9 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt @@ -30,6 +30,6 @@ import org.gradle.api.Project internal fun LibraryAndroidComponentsExtension.disableUnnecessaryAndroidTests( project: Project, ) = beforeVariants { - it.enableAndroidTest = it.enableAndroidTest + it.androidTest.enable = it.androidTest.enable && project.projectDir.resolve("src/androidTest").exists() } 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 bfb799595..57f026029 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 @@ -90,5 +90,19 @@ private inline fun Project.configureKotlin // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", ) + freeCompilerArgs.add( + /** + * Remove this args after Phase 3. + * https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-consistent-copy-visibility/#deprecation-timeline + * + * Deprecation timeline + * Phase 3. (Supposedly Kotlin 2.2 or Kotlin 2.3). + * The default changes. + * Unless ExposedCopyVisibility is used, the generated 'copy' method has the same visibility as the primary constructor. + * The binary signature changes. The error on the declaration is no longer reported. + * '-Xconsistent-data-class-copy-visibility' compiler flag and ConsistentCopyVisibility annotation are now unnecessary. + */ + "-Xconsistent-data-class-copy-visibility" + ) } } diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index de9224e22..b359a5207 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -16,7 +16,13 @@ dependencyResolutionManagement { repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() } versionCatalogs { diff --git a/build.gradle.kts b/build.gradle.kts index fbcefa906..9a8652956 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,13 @@ buildscript { repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() // Android Build Server 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 4ac19b482..4a2099dc6 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 @@ -28,10 +28,8 @@ import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.NavigationRailItemDefaults import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.adaptive.navigationsuite.ExperimentalMaterial3AdaptiveNavigationSuiteApi import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaults import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold @@ -184,10 +182,6 @@ fun NiaNavigationRail( * @param windowAdaptiveInfo The window adaptive info. * @param content The app content inside the scaffold. */ -@OptIn( - ExperimentalMaterial3AdaptiveNavigationSuiteApi::class, - ExperimentalMaterial3AdaptiveApi::class, -) @Composable fun NiaNavigationSuiteScaffold( navigationSuiteItems: NiaNavigationSuiteScope.() -> Unit, @@ -242,7 +236,6 @@ fun NiaNavigationSuiteScaffold( /** * A wrapper around [NavigationSuiteScope] to declare navigation items. */ -@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class) class NiaNavigationSuiteScope internal constructor( private val navigationSuiteScope: NavigationSuiteScope, private val navigationSuiteItemColors: NavigationSuiteItemColors, diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt index 77dfa4394..a56bbcb8d 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/UserNewsResource.kt @@ -22,7 +22,6 @@ import kotlinx.datetime.Instant * A [NewsResource] with additional user information such as whether the user is following the * news resource's topics and whether they have saved (bookmarked) this news resource. */ -@ConsistentCopyVisibility data class UserNewsResource internal constructor( val id: String, val title: String, diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index c22a02fa1..0b7b9f570 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -21,7 +21,6 @@ import android.net.Uri import androidx.annotation.ColorInt import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope @@ -45,7 +44,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserNewsResource * An extension on [LazyListScope] defining a feed with news resources. * Depending on the [feedState], this might emit no items. */ -@OptIn(ExperimentalFoundationApi::class) fun LazyStaggeredGridScope.newsFeed( feedState: NewsFeedUiState, onNewsResourcesCheckedChanged: (String, Boolean) -> Unit, diff --git a/docs/images/screenshot-1-foryou.png b/docs/images/screenshot-1-foryou.png deleted file mode 100644 index 0d76fce79..000000000 Binary files a/docs/images/screenshot-1-foryou.png and /dev/null differ diff --git a/docs/images/screenshot-2-interests.png b/docs/images/screenshot-2-interests.png deleted file mode 100644 index 1137e59c3..000000000 Binary files a/docs/images/screenshot-2-interests.png and /dev/null differ diff --git a/docs/images/screenshot-3-topicdetail.png b/docs/images/screenshot-3-topicdetail.png deleted file mode 100644 index d5150d376..000000000 Binary files a/docs/images/screenshot-3-topicdetail.png and /dev/null differ diff --git a/docs/images/screenshots.png b/docs/images/screenshots.png index f92fe6455..3f143bc6c 100644 Binary files a/docs/images/screenshots.png and b/docs/images/screenshots.png differ diff --git a/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt b/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt index 40f54e4a7..dd01b84c9 100644 --- a/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt +++ b/feature/bookmarks/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt @@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.feature.bookmarks import androidx.activity.ComponentActivity import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.assertCountEquals import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.filter @@ -33,6 +32,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner import com.google.samples.apps.nowinandroid.core.testing.data.userNewsResourcesTestData import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState diff --git a/feature/foryou/build.gradle.kts b/feature/foryou/build.gradle.kts index 41d5b16a2..59f6844cf 100644 --- a/feature/foryou/build.gradle.kts +++ b/feature/foryou/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { implementation(libs.accompanist.permissions) implementation(projects.core.data) implementation(projects.core.domain) - implementation(project(":core:notifications")) + implementation(projects.core.notifications) testImplementation(libs.hilt.android.testing) testImplementation(libs.robolectric) 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 ff91941a8..b617f98a9 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 @@ -522,6 +522,7 @@ private fun SearchTextField( .focusRequester(focusRequester) .onKeyEvent { if (it.key == Key.Enter) { + if (searchQuery.isBlank()) return@onKeyEvent false onSearchExplicitlyTriggered() true } else { @@ -536,6 +537,7 @@ private fun SearchTextField( ), keyboardActions = KeyboardActions( onSearch = { + if (searchQuery.isBlank()) return@KeyboardActions onSearchExplicitlyTriggered() }, ), diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt index 6c2af240c..36947880e 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt @@ -59,7 +59,7 @@ class SearchViewModel @Inject constructor( flowOf(SearchResultUiState.SearchNotReady) } else { searchQuery.flatMapLatest { query -> - if (query.length < SEARCH_QUERY_MIN_LENGTH) { + if (query.trim().length < SEARCH_QUERY_MIN_LENGTH) { flowOf(SearchResultUiState.EmptyQuery) } else { getSearchContentsUseCase(query) @@ -102,6 +102,7 @@ class SearchViewModel @Inject constructor( * search query in the search text field, defining this method. */ fun onSearchTriggered(query: String) { + if (query.isBlank()) return viewModelScope.launch { recentSearchRepository.insertOrReplaceRecentSearch(searchQuery = query) } diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt index 81f3576b4..3b16e5f71 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt @@ -21,10 +21,12 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.search.SearchRoute +import kotlinx.serialization.Serializable -const val SEARCH_ROUTE = "search_route" +@Serializable data object SearchRoute -fun NavController.navigateToSearch(navOptions: NavOptions? = null) = navigate(SEARCH_ROUTE, navOptions) +fun NavController.navigateToSearch(navOptions: NavOptions? = null) = + navigate(SearchRoute, navOptions) fun NavGraphBuilder.searchScreen( onBackClick: () -> Unit, @@ -33,7 +35,7 @@ fun NavGraphBuilder.searchScreen( ) { // TODO: Handle back stack for each top-level destination. At the moment each top-level // destination may have own search screen's back stack. - composable(route = SEARCH_ROUTE) { + composable { SearchRoute( onBackClick = onBackClick, onInterestsClick = onInterestsClick, diff --git a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt index a62965b52..1b866cec2 100644 --- a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt +++ b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt @@ -41,6 +41,7 @@ import org.junit.Rule import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNull /** * To learn more about how this test handles Flows created with stateIn, see @@ -122,6 +123,43 @@ class SearchViewModelTest { assertEquals(SearchNotReady, viewModel.searchResultUiState.value) } + @Test + fun emptySearchText_isNotAddedToRecentSearches() = runTest { + viewModel.onSearchTriggered("") + + val recentSearchQueriesStream = getRecentQueryUseCase() + val recentSearchQueries = recentSearchQueriesStream.first() + val recentSearchQuery = recentSearchQueries.firstOrNull() + + assertNull(recentSearchQuery) + } + + @Test + fun searchTextWithThreeSpaces_isEmptyQuery() = runTest { + searchContentsRepository.addNewsResources(newsResourcesTestData) + searchContentsRepository.addTopics(topicsTestData) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + + viewModel.onSearchQueryChanged(" ") + + assertIs(viewModel.searchResultUiState.value) + + collectJob.cancel() + } + + @Test + fun searchTextWithThreeSpacesAndOneLetter_isEmptyQuery() = runTest { + searchContentsRepository.addNewsResources(newsResourcesTestData) + searchContentsRepository.addTopics(topicsTestData) + val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + + viewModel.onSearchQueryChanged(" a") + + assertIs(viewModel.searchResultUiState.value) + + collectJob.cancel() + } + @Test fun whenToggleNewsResourceSavedIsCalled_bookmarkStateIsUpdated() = runTest { val newsResourceId = "123" diff --git a/generateModuleGraphs.sh b/generateModuleGraphs.sh index 3c3583e67..5307d2932 100755 --- a/generateModuleGraphs.sh +++ b/generateModuleGraphs.sh @@ -27,7 +27,8 @@ then echo "The 'dot' command is not found. This is required to generate SVGs from the Graphviz files." echo "Installation instructions:" echo " - On macOS: You can install Graphviz using Homebrew with the command: 'brew install graphviz'" - echo " - On Ubuntu: You can install Graphviz using APT with the command: 'sudo apt-get install graphviz'" + echo " - On Ubuntu: You can install Graphviz using APT with the command: 'sudo apt install graphviz'" + echo " - Others: Visit https://graphviz.org/download/" exit 1 fi diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index fe79fa01e..44dc41200 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -14,10 +14,10 @@ * limitations under the License. */ -val ktlintVersion = "1.0.1" +val ktlintVersion = "1.4.0" initscript { - val spotlessVersion = "6.23.3" + val spotlessVersion = "6.25.0" repositories { mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f2c0bedb2..4e996177a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -accompanist = "0.34.0" -androidDesugarJdkLibs = "2.0.4" +accompanist = "0.36.0" +androidDesugarJdkLibs = "2.1.3" # AGP and tools should be updated together androidGradlePlugin = "8.6.1" -androidTools = "31.7.2" +androidTools = "31.7.3" androidxActivity = "1.9.3" androidxAppCompat = "1.7.0" androidxBrowser = "1.8.0" @@ -14,11 +14,11 @@ androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.1.1" androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" -androidxLifecycle = "2.8.6" -androidxMacroBenchmark = "1.3.0" +androidxLifecycle = "2.8.7" +androidxMacroBenchmark = "1.3.3" androidxMetrics = "1.0.0-beta01" -androidxNavigation = "2.8.0" -androidxProfileinstaller = "1.3.1" +androidxNavigation = "2.8.4" +androidxProfileinstaller = "1.4.1" androidxTestCore = "1.6.1" androidxTestExt = "1.2.1" androidxTestRules = "1.6.1" @@ -26,13 +26,13 @@ androidxTestRunner = "1.6.2" androidxTracing = "1.3.0-alpha02" androidxUiAutomator = "2.3.0" androidxWindowManager = "1.3.0" -androidxWork = "2.9.0" +androidxWork = "2.9.1" coil = "2.7.0" dependencyGuard = "0.5.0" -firebaseBom = "33.3.0" -firebaseCrashlyticsPlugin = "2.9.9" +firebaseBom = "33.7.0" +firebaseCrashlyticsPlugin = "3.0.2" firebasePerfPlugin = "1.4.2" -gmsPlugin = "4.4.1" +gmsPlugin = "4.4.2" googleOss = "17.1.0" googleOssPlugin = "0.10.6" hilt = "2.52" @@ -46,15 +46,15 @@ kotlinxSerializationJson = "1.6.3" ksp = "2.0.20-1.0.25" moduleGraph = "2.7.1" okhttp = "4.12.0" -protobuf = "4.26.1" +protobuf = "4.28.2" protobufPlugin = "0.9.4" retrofit = "2.11.0" retrofitKotlinxSerializationJson = "1.0.0" robolectric = "4.14.1" -roborazzi = "1.33.0" +roborazzi = "1.36.0" room = "2.6.1" secrets = "2.0.1" -truth = "1.4.2" +truth = "1.4.4" turbine = "1.1.0" [bundles] diff --git a/lint/build.gradle.kts b/lint/build.gradle.kts index 99a057362..f1722fa78 100644 --- a/lint/build.gradle.kts +++ b/lint/build.gradle.kts @@ -38,7 +38,7 @@ kotlin { dependencies { compileOnly(libs.kotlin.stdlib) compileOnly(libs.lint.api) + testImplementation(libs.kotlin.test) testImplementation(libs.lint.checks) testImplementation(libs.lint.tests) - testImplementation(kotlin("test")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 465a72616..2b8c6e45c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,7 +17,13 @@ pluginManagement { includeBuild("build-logic") repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() gradlePluginPortal() } @@ -26,7 +32,13 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() } }