From 03ba943632bf8e3b799c189bf2f80117faac0ef9 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 16 Sep 2023 15:19:58 +0200 Subject: [PATCH 01/71] Extract `ProfileVerifier` logs from `MainActivity` to `NiaApplication` - Prevent logging multiple times (because of `onResume()` callback) - Remove unnecessary io dispatcher: `ListenableFuture.await()` is already main-safe and prevent acquiring io slot. - Merge comments into a single javadoc comment on the `ProfileVerifierLogger` type. - Add proper javadoc format with markdown specific blocks. - Update logs & tag to be more uniform. Closes #945 --- .../samples/apps/nowinandroid/MainActivity.kt | 41 ----------- .../apps/nowinandroid/NiaApplication.kt | 5 ++ .../util/ProfileVerifierLogger.kt | 70 +++++++++++++++++++ 3 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt index 7fe1bc674..42468ca6f 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -17,7 +17,6 @@ package com.google.samples.apps.nowinandroid import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent @@ -37,7 +36,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.metrics.performance.JankStats -import androidx.profileinstaller.ProfileVerifier import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper @@ -49,12 +47,9 @@ import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.ui.NiaApp import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.guava.await import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import javax.inject.Inject private const val TAG = "MainActivity" @@ -152,48 +147,12 @@ class MainActivity : ComponentActivity() { override fun onResume() { super.onResume() lazyStats.get().isTrackingEnabled = true - lifecycleScope.launch { - logCompilationStatus() - } } override fun onPause() { super.onPause() lazyStats.get().isTrackingEnabled = false } - - /** - * Logs the app's Baseline Profile Compilation Status using [ProfileVerifier]. - */ - private suspend fun logCompilationStatus() { - /* - When delivering through Google Play, the baseline profile is compiled during installation. - In this case you will see the correct state logged without any further action necessary. - To verify baseline profile installation locally, you need to manually trigger baseline - profile installation. - For immediate compilation, call: - `adb shell cmd package compile -f -m speed-profile com.example.macrobenchmark.target` - You can also trigger background optimizations: - `adb shell pm bg-dexopt-job` - Both jobs run asynchronously and might take some time complete. - To see quick turnaround of the ProfileVerifier, we recommend using `speed-profile`. - If you don't do either of these steps, you might only see the profile status reported as - "enqueued for compilation" when running the sample locally. - */ - withContext(Dispatchers.IO) { - val status = ProfileVerifier.getCompilationStatusAsync().await() - Log.d(TAG, "ProfileInstaller status code: ${status.profileInstallResultCode}") - Log.d( - TAG, - when { - status.isCompiledWithProfile -> "ProfileInstaller: is compiled with profile" - status.hasProfileEnqueuedForCompilation() -> - "ProfileInstaller: Enqueued for compilation" - else -> "Profile not compiled or enqueued" - }, - ) - } - } } /** diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/NiaApplication.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/NiaApplication.kt index 699f52575..9f0bb2ef7 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/NiaApplication.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/NiaApplication.kt @@ -20,6 +20,7 @@ import android.app.Application import coil.ImageLoader import coil.ImageLoaderFactory import com.google.samples.apps.nowinandroid.sync.initializers.Sync +import com.google.samples.apps.nowinandroid.util.ProfileVerifierLogger import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject import javax.inject.Provider @@ -32,10 +33,14 @@ class NiaApplication : Application(), ImageLoaderFactory { @Inject lateinit var imageLoader: Provider + @Inject + lateinit var profileVerifierLogger: ProfileVerifierLogger + override fun onCreate() { super.onCreate() // Initialize Sync; the system responsible for keeping data in the app up to date. Sync.initialize(context = this) + profileVerifierLogger() } override fun newImageLoader(): ImageLoader = imageLoader.get() diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt new file mode 100644 index 000000000..595166f03 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.util + +import android.util.Log +import androidx.profileinstaller.ProfileVerifier +import com.google.samples.apps.nowinandroid.core.network.di.ApplicationScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.guava.await +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * Logs the app's Baseline Profile Compilation Status using [ProfileVerifier]. + * + * When delivering through Google Play, the baseline profile is compiled during installation. + * In this case you will see the correct state logged without any further action necessary. + * To verify baseline profile installation locally, you need to manually trigger baseline + * profile installation. + * + * For immediate compilation, call: + * ```bash + * adb shell cmd package compile -f -m speed-profile com.example.macrobenchmark.target + * ``` + * You can also trigger background optimizations: + * ```bash + * adb shell pm bg-dexopt-job + * ``` + * Both jobs run asynchronously and might take some time complete. + * + * To see quick turnaround of the ProfileVerifier, we recommend using `speed-profile`. + * If you don't do either of these steps, you might only see the profile status reported as + * "enqueued for compilation" when running the sample locally. + * + * @see androidx.profileinstaller.ProfileVerifier.CompilationStatus.ResultCode + */ +class ProfileVerifierLogger @Inject constructor( + @ApplicationScope private val scope: CoroutineScope, +) { + companion object { + private const val TAG = "ProfileInstaller" + } + + operator fun invoke() = scope.launch { + val status = ProfileVerifier.getCompilationStatusAsync().await() + Log.d(TAG, "Status code: ${status.profileInstallResultCode}") + Log.d( + TAG, + when { + status.isCompiledWithProfile -> "App compiled with profile" + status.hasProfileEnqueuedForCompilation() -> "Profile enqueued for compilation" + else -> "Profile not compiled nor enqueued" + }, + ) + } +} From ebfe01affd6b420e815fe29d5c8185c8e80183f7 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Mon, 29 May 2023 13:03:27 +0200 Subject: [PATCH 02/71] Enforce `resourcePrefix` on Android library modules ``` :ui-test-hilt-manifest -> ui_test_hilt_manifest_ :core:analytics -> core_analytics_ :core:common -> core_common_ :core:data -> core_data_ :core:data-test -> core_data_test_ :core:database -> core_database_ :core:datastore -> core_datastore_ :core:datastore-test -> core_datastore_test_ :core:designsystem -> core_designsystem_ :core:domain -> core_domain_ :core:network -> core_network_ :core:notifications -> core_notifications_ :core:testing -> core_testing_ :core:ui -> core_ui_ :feature:bookmarks -> feature_bookmarks_ :feature:foryou -> feature_foryou_ :feature:interests -> feature_interests_ :feature:search -> feature_search_ :feature:settings -> feature_settings_ :feature:topic -> feature_topic_ :sync:sync-test -> sync_test_ :sync:work -> sync_work_ ``` --- .../apps/nowinandroid/ui/NavigationTest.kt | 16 +++---- .../navigation/TopLevelDestination.kt | 12 ++--- .../samples/apps/nowinandroid/ui/NiaApp.kt | 4 +- .../kotlin/AndroidLibraryConventionPlugin.kt | 3 ++ ...ml => core_common_ic_nia_notification.xml} | 0 ...ng => core_common_ic_nia_notification.png} | Bin ...ng => core_common_ic_nia_notification.png} | Bin ...ng => core_common_ic_nia_notification.png} | Bin ...ng => core_common_ic_nia_notification.png} | Bin .../component/DynamicAsyncImage.kt | 2 +- ...e_designsystem_ic_placeholder_default.xml} | 0 .../core/notifications/SystemTrayNotifier.kt | 10 ++-- .../src/main/res/values/strings.xml | 6 +-- .../core/ui/NewsResourceCardTest.kt | 6 +-- .../nowinandroid/core/ui/NewsResourceCard.kt | 16 +++---- core/ui/src/main/res/values/strings.xml | 16 +++---- .../feature/bookmarks/BookmarksScreenTest.kt | 8 ++-- .../feature/bookmarks/BookmarksScreen.kt | 12 ++--- ...feature_bookmarks_img_empty_bookmarks.xml} | 0 .../bookmarks/src/main/res/values/strings.xml | 12 ++--- .../feature/foryou/ForYouScreenTest.kt | 10 ++-- .../feature/foryou/ForYouScreen.kt | 10 ++-- ...=> feature_foryou_ic_icon_placeholder.xml} | 0 .../foryou/src/main/res/values/strings.xml | 12 ++--- .../interests/InterestsScreenTest.kt | 8 ++-- .../feature/interests/InterestsItem.kt | 4 +- .../feature/interests/InterestsScreen.kt | 4 +- .../interests/src/main/res/values/strings.xml | 10 ++-- .../feature/search/SearchScreenTest.kt | 18 +++---- .../feature/search/SearchScreen.kt | 24 +++++----- .../search/src/main/res/values/strings.xml | 22 ++++----- .../feature/settings/SettingsDialogTest.kt | 44 +++++++++--------- .../feature/settings/SettingsDialog.kt | 34 +++++++------- .../settings/src/main/res/values/strings.xml | 38 +++++++-------- .../feature/topic/TopicScreenTest.kt | 2 +- .../nowinandroid/feature/topic/TopicScreen.kt | 4 +- feature/topic/src/main/res/values/strings.xml | 2 +- .../sync/initializers/SyncWorkHelpers.kt | 8 ++-- sync/work/src/main/res/values/strings.xml | 6 +-- 39 files changed, 193 insertions(+), 190 deletions(-) rename core/common/src/main/res/drawable-anydpi-v24/{ic_nia_notification.xml => core_common_ic_nia_notification.xml} (100%) rename core/common/src/main/res/drawable-hdpi/{ic_nia_notification.png => core_common_ic_nia_notification.png} (100%) rename core/common/src/main/res/drawable-mdpi/{ic_nia_notification.png => core_common_ic_nia_notification.png} (100%) rename core/common/src/main/res/drawable-xhdpi/{ic_nia_notification.png => core_common_ic_nia_notification.png} (100%) rename core/common/src/main/res/drawable-xxhdpi/{ic_nia_notification.png => core_common_ic_nia_notification.png} (100%) rename core/designsystem/src/main/res/drawable/{ic_placeholder_default.xml => core_designsystem_ic_placeholder_default.xml} (100%) rename feature/bookmarks/src/main/res/drawable/{img_empty_bookmarks.xml => feature_bookmarks_img_empty_bookmarks.xml} (100%) rename feature/foryou/src/main/res/drawable/{ic_icon_placeholder.xml => feature_foryou_ic_icon_placeholder.xml} (100%) diff --git a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt index e1eab4796..6515c14de 100644 --- a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt +++ b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt @@ -51,7 +51,7 @@ import javax.inject.Inject import kotlin.properties.ReadOnlyProperty import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR import com.google.samples.apps.nowinandroid.feature.foryou.R as FeatureForyouR -import com.google.samples.apps.nowinandroid.feature.interests.R as FeatureInterestsR +import com.google.samples.apps.nowinandroid.feature.search.R as FeatureSearchR import com.google.samples.apps.nowinandroid.feature.settings.R as SettingsR /** @@ -93,15 +93,15 @@ class NavigationTest { ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests - private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up) - private val forYou by composeTestRule.stringResource(FeatureForyouR.string.for_you) - private val interests by composeTestRule.stringResource(FeatureInterestsR.string.interests) + private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_navigate_up) + private val forYou by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_title) + private val interests by composeTestRule.stringResource(FeatureSearchR.string.feature_search_interests) private val sampleTopic = "Headlines" private val appName by composeTestRule.stringResource(R.string.app_name) - private val saved by composeTestRule.stringResource(BookmarksR.string.saved) - private val settings by composeTestRule.stringResource(SettingsR.string.top_app_bar_action_icon_description) - private val brand by composeTestRule.stringResource(SettingsR.string.brand_android) - private val ok by composeTestRule.stringResource(SettingsR.string.dismiss_dialog_button_text) + private val saved by composeTestRule.stringResource(BookmarksR.string.feature_bookmarks_title) + private val settings by composeTestRule.stringResource(SettingsR.string.feature_settings_top_app_bar_action_icon_description) + private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_brand_android) + private val ok by composeTestRule.stringResource(SettingsR.string.feature_settings_dismiss_dialog_button_text) @Before fun setup() = hiltRule.inject() diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt index 8dbd0fcb6..aca7d54ab 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/navigation/TopLevelDestination.kt @@ -21,7 +21,7 @@ import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR -import com.google.samples.apps.nowinandroid.feature.interests.R as interestsR +import com.google.samples.apps.nowinandroid.feature.search.R as searchR /** * Type for the top level destinations in the application. Each of these destinations @@ -37,19 +37,19 @@ enum class TopLevelDestination( FOR_YOU( selectedIcon = NiaIcons.Upcoming, unselectedIcon = NiaIcons.UpcomingBorder, - iconTextId = forYouR.string.for_you, + iconTextId = forYouR.string.feature_foryou_title, titleTextId = R.string.app_name, ), BOOKMARKS( selectedIcon = NiaIcons.Bookmarks, unselectedIcon = NiaIcons.BookmarksBorder, - iconTextId = bookmarksR.string.saved, - titleTextId = bookmarksR.string.saved, + iconTextId = bookmarksR.string.feature_bookmarks_title, + titleTextId = bookmarksR.string.feature_bookmarks_title, ), INTERESTS( selectedIcon = NiaIcons.Grid3x3, unselectedIcon = NiaIcons.Grid3x3, - iconTextId = interestsR.string.interests, - titleTextId = interestsR.string.interests, + iconTextId = searchR.string.feature_search_interests, + titleTextId = searchR.string.feature_search_interests, ), } diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt index aa85afebd..1660581a4 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NiaApp.kt @@ -183,11 +183,11 @@ fun NiaApp( titleRes = destination.titleTextId, navigationIcon = NiaIcons.Search, navigationIconContentDescription = stringResource( - id = settingsR.string.top_app_bar_navigation_icon_description, + id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description, ), actionIcon = NiaIcons.Settings, actionIconContentDescription = stringResource( - id = settingsR.string.top_app_bar_action_icon_description, + id = settingsR.string.feature_settings_top_app_bar_action_icon_description, ), colors = TopAppBarDefaults.centerAlignedTopAppBarColors( containerColor = Color.Transparent, diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index ef84cfbb4..995b922a2 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -41,6 +41,9 @@ class AndroidLibraryConventionPlugin : Plugin { defaultConfig.targetSdk = 34 configureFlavors(this) configureGradleManagedDevices(this) + // The resource prefix is derived from the module name, + // so resources inside ":core:module1" must be prefixed with "core_module1_" + resourcePrefix = path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_").lowercase() + "_" } extensions.configure { configurePrintApksTask(this) diff --git a/core/common/src/main/res/drawable-anydpi-v24/ic_nia_notification.xml b/core/common/src/main/res/drawable-anydpi-v24/core_common_ic_nia_notification.xml similarity index 100% rename from core/common/src/main/res/drawable-anydpi-v24/ic_nia_notification.xml rename to core/common/src/main/res/drawable-anydpi-v24/core_common_ic_nia_notification.xml diff --git a/core/common/src/main/res/drawable-hdpi/ic_nia_notification.png b/core/common/src/main/res/drawable-hdpi/core_common_ic_nia_notification.png similarity index 100% rename from core/common/src/main/res/drawable-hdpi/ic_nia_notification.png rename to core/common/src/main/res/drawable-hdpi/core_common_ic_nia_notification.png diff --git a/core/common/src/main/res/drawable-mdpi/ic_nia_notification.png b/core/common/src/main/res/drawable-mdpi/core_common_ic_nia_notification.png similarity index 100% rename from core/common/src/main/res/drawable-mdpi/ic_nia_notification.png rename to core/common/src/main/res/drawable-mdpi/core_common_ic_nia_notification.png diff --git a/core/common/src/main/res/drawable-xhdpi/ic_nia_notification.png b/core/common/src/main/res/drawable-xhdpi/core_common_ic_nia_notification.png similarity index 100% rename from core/common/src/main/res/drawable-xhdpi/ic_nia_notification.png rename to core/common/src/main/res/drawable-xhdpi/core_common_ic_nia_notification.png diff --git a/core/common/src/main/res/drawable-xxhdpi/ic_nia_notification.png b/core/common/src/main/res/drawable-xxhdpi/core_common_ic_nia_notification.png similarity index 100% rename from core/common/src/main/res/drawable-xxhdpi/ic_nia_notification.png rename to core/common/src/main/res/drawable-xxhdpi/core_common_ic_nia_notification.png diff --git a/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt b/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt index cc352107b..bd22fa168 100644 --- a/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt +++ b/core/designsystem/src/main/java/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt @@ -49,7 +49,7 @@ fun DynamicAsyncImage( imageUrl: String, contentDescription: String?, modifier: Modifier = Modifier, - placeholder: Painter = painterResource(R.drawable.ic_placeholder_default), + placeholder: Painter = painterResource(R.drawable.core_designsystem_ic_placeholder_default), ) { val iconTint = LocalTintTheme.current.iconTint var isLoading by remember { mutableStateOf(true) } diff --git a/core/designsystem/src/main/res/drawable/ic_placeholder_default.xml b/core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml similarity index 100% rename from core/designsystem/src/main/res/drawable/ic_placeholder_default.xml rename to core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml diff --git a/core/notifications/src/main/java/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt b/core/notifications/src/main/java/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt index b7fcc9b26..12691b007 100644 --- a/core/notifications/src/main/java/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt +++ b/core/notifications/src/main/java/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt @@ -72,7 +72,7 @@ class SystemTrayNotifier @Inject constructor( .map { newsResource -> createNewsNotification { setSmallIcon( - com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification, + com.google.samples.apps.nowinandroid.core.common.R.drawable.core_common_ic_nia_notification, ) .setContentTitle(newsResource.title) .setContentText(newsResource.content) @@ -83,13 +83,13 @@ class SystemTrayNotifier @Inject constructor( } val summaryNotification = createNewsNotification { val title = getString( - R.string.news_notification_group_summary, + R.string.core_notifications_news_notification_group_summary, truncatedNewsResources.size, ) setContentTitle(title) .setContentText(title) .setSmallIcon( - com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification, + com.google.samples.apps.nowinandroid.core.common.R.drawable.core_common_ic_nia_notification, ) // Build summary info into InboxStyle template. .setStyle(newsNotificationStyle(truncatedNewsResources, title)) @@ -148,10 +148,10 @@ private fun Context.ensureNotificationChannelExists() { val channel = NotificationChannel( NEWS_NOTIFICATION_CHANNEL_ID, - getString(R.string.news_notification_channel_name), + getString(R.string.core_notifications_news_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT, ).apply { - description = getString(R.string.news_notification_channel_description) + description = getString(R.string.core_notifications_news_notification_channel_description) } // Register the channel with the system NotificationManagerCompat.from(this).createNotificationChannel(channel) diff --git a/core/notifications/src/main/res/values/strings.xml b/core/notifications/src/main/res/values/strings.xml index 5bb37b23a..88e492e77 100644 --- a/core/notifications/src/main/res/values/strings.xml +++ b/core/notifications/src/main/res/values/strings.xml @@ -15,7 +15,7 @@ limitations under the License. --> - News updates - The latest updates on what\'s new in Android - %1$d news updates + News updates + The latest updates on what\'s new in Android + %1$d news updates diff --git a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt index a2fdbaee1..d0a124316 100644 --- a/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt +++ b/core/ui/src/androidTest/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCardTest.kt @@ -52,7 +52,7 @@ class NewsResourceCardTest { composeTestRule .onNodeWithText( composeTestRule.activity.getString( - R.string.card_meta_data_text, + R.string.core_ui_card_meta_data_text, dateFormatted, newsWithKnownResourceType.type, ), @@ -123,7 +123,7 @@ class NewsResourceCardTest { composeTestRule .onNodeWithContentDescription( composeTestRule.activity.getString( - R.string.unread_resource_dot_content_description, + R.string.core_ui_unread_resource_dot_content_description, ), ) .assertIsDisplayed() @@ -147,7 +147,7 @@ class NewsResourceCardTest { composeTestRule .onNodeWithContentDescription( composeTestRule.activity.getString( - R.string.unread_resource_dot_content_description, + R.string.core_ui_unread_resource_dot_content_description, ), ) .assertDoesNotExist() diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index de4aec9d7..687604ccc 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -91,7 +91,7 @@ fun NewsResourceCardExpanded( onTopicClick: (String) -> Unit, modifier: Modifier = Modifier, ) { - val clickActionLabel = stringResource(R.string.card_tap_action) + val clickActionLabel = stringResource(R.string.core_ui_card_tap_action) Card( onClick = onClick, shape = RoundedCornerShape(16.dp), @@ -183,7 +183,7 @@ fun NewsResourceHeaderImage( painter = if (isError.not() && !isLocalInspection) { imageLoader } else { - painterResource(drawable.ic_placeholder_default) + painterResource(drawable.core_designsystem_ic_placeholder_default) }, // TODO b/226661685: Investigate using alt text of image to populate content description contentDescription = null, // decorative image, @@ -212,13 +212,13 @@ fun BookmarkButton( icon = { Icon( imageVector = NiaIcons.BookmarkBorder, - contentDescription = stringResource(R.string.bookmark), + contentDescription = stringResource(R.string.core_ui_bookmark), ) }, checkedIcon = { Icon( imageVector = NiaIcons.Bookmark, - contentDescription = stringResource(R.string.unbookmark), + contentDescription = stringResource(R.string.core_ui_unbookmark), ) }, ) @@ -229,7 +229,7 @@ fun NotificationDot( color: Color, modifier: Modifier = Modifier, ) { - val description = stringResource(R.string.unread_resource_dot_content_description) + val description = stringResource(R.string.core_ui_unread_resource_dot_content_description) Canvas( modifier = modifier .semantics { contentDescription = description }, @@ -273,7 +273,7 @@ fun NewsResourceMetaData( val formattedDate = dateFormatted(publishDate) Text( if (resourceType.isNotBlank()) { - stringResource(R.string.card_meta_data_text, formattedDate, resourceType) + stringResource(R.string.core_ui_card_meta_data_text, formattedDate, resourceType) } else { formattedDate }, @@ -305,12 +305,12 @@ fun NewsResourceTopics( text = { val contentDescription = if (followableTopic.isFollowed) { stringResource( - R.string.topic_chip_content_description_when_followed, + R.string.core_ui_topic_chip_content_description_when_followed, followableTopic.topic.name, ) } else { stringResource( - R.string.topic_chip_content_description_when_not_followed, + R.string.core_ui_topic_chip_content_description_when_not_followed, followableTopic.topic.name, ) } diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index d21a5ea36..65a855fc9 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -15,15 +15,15 @@ limitations under the License. --> - Bookmark - Unbookmark - Back + Bookmark + Unbookmark + Back - Unread + Unread - Open Resource Link - %1$s • %2$s + Open Resource Link + %1$s • %2$s - %1$s is followed - %1$s is not followed + %1$s is followed + %1$s is not followed diff --git a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt index 6e432f2ab..3d684f9d1 100644 --- a/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt +++ b/feature/bookmarks/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreenTest.kt @@ -59,7 +59,7 @@ class BookmarksScreenTest { composeTestRule .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.saved_loading), + composeTestRule.activity.resources.getString(R.string.feature_bookmarks_loading), ) .assertExists() } @@ -125,7 +125,7 @@ class BookmarksScreenTest { composeTestRule .onAllNodesWithContentDescription( composeTestRule.activity.getString( - com.google.samples.apps.nowinandroid.core.ui.R.string.unbookmark, + com.google.samples.apps.nowinandroid.core.ui.R.string.core_ui_unbookmark, ), ).filter( hasAnyAncestor( @@ -156,13 +156,13 @@ class BookmarksScreenTest { composeTestRule .onNodeWithText( - composeTestRule.activity.getString(R.string.bookmarks_empty_error), + composeTestRule.activity.getString(R.string.feature_bookmarks_empty_error), ) .assertExists() composeTestRule .onNodeWithText( - composeTestRule.activity.getString(R.string.bookmarks_empty_description), + composeTestRule.activity.getString(R.string.feature_bookmarks_empty_description), ) .assertExists() } diff --git a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt index e46ada015..c84263405 100644 --- a/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt +++ b/feature/bookmarks/src/main/java/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt @@ -113,8 +113,8 @@ internal fun BookmarksScreen( undoBookmarkRemoval: () -> Unit = {}, clearUndoState: () -> Unit = {}, ) { - val bookmarkRemovedMessage = stringResource(id = R.string.bookmark_removed) - val undoText = stringResource(id = R.string.undo) + val bookmarkRemovedMessage = stringResource(id = R.string.feature_bookmarks_removed) + val undoText = stringResource(id = R.string.feature_bookmarks_undo) LaunchedEffect(shouldDisplayUndoBookmark) { if (shouldDisplayUndoBookmark) { @@ -163,7 +163,7 @@ private fun LoadingState(modifier: Modifier = Modifier) { .fillMaxWidth() .wrapContentSize() .testTag("forYou:loading"), - contentDesc = stringResource(id = R.string.saved_loading), + contentDesc = stringResource(id = R.string.feature_bookmarks_loading), ) } @@ -236,7 +236,7 @@ private fun EmptyState(modifier: Modifier = Modifier) { val iconTint = LocalTintTheme.current.iconTint Image( modifier = Modifier.fillMaxWidth(), - painter = painterResource(id = R.drawable.img_empty_bookmarks), + painter = painterResource(id = R.drawable.feature_bookmarks_img_empty_bookmarks), colorFilter = if (iconTint != null) ColorFilter.tint(iconTint) else null, contentDescription = null, ) @@ -244,7 +244,7 @@ private fun EmptyState(modifier: Modifier = Modifier) { Spacer(modifier = Modifier.height(48.dp)) Text( - text = stringResource(id = R.string.bookmarks_empty_error), + text = stringResource(id = R.string.feature_bookmarks_empty_error), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, style = MaterialTheme.typography.titleMedium, @@ -254,7 +254,7 @@ private fun EmptyState(modifier: Modifier = Modifier) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = stringResource(id = R.string.bookmarks_empty_description), + text = stringResource(id = R.string.feature_bookmarks_empty_description), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium, diff --git a/feature/bookmarks/src/main/res/drawable/img_empty_bookmarks.xml b/feature/bookmarks/src/main/res/drawable/feature_bookmarks_img_empty_bookmarks.xml similarity index 100% rename from feature/bookmarks/src/main/res/drawable/img_empty_bookmarks.xml rename to feature/bookmarks/src/main/res/drawable/feature_bookmarks_img_empty_bookmarks.xml diff --git a/feature/bookmarks/src/main/res/values/strings.xml b/feature/bookmarks/src/main/res/values/strings.xml index 875a90a0b..6e2b23043 100644 --- a/feature/bookmarks/src/main/res/values/strings.xml +++ b/feature/bookmarks/src/main/res/values/strings.xml @@ -15,10 +15,10 @@ limitations under the License. --> - Saved - Loading saved… - No saved updates - Updates you save will be stored here\nto read later - Bookmark removed - UNDO + Saved + Loading saved… + No saved updates + Updates you save will be stored here\nto read later + Bookmark removed + UNDO diff --git a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt index 7431555ba..5477493ef 100644 --- a/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt +++ b/feature/foryou/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenTest.kt @@ -45,7 +45,7 @@ class ForYouScreenTest { private val doneButtonMatcher by lazy { hasText( - composeTestRule.activity.resources.getString(R.string.done), + composeTestRule.activity.resources.getString(R.string.feature_foryou_done), ) } @@ -70,7 +70,7 @@ class ForYouScreenTest { composeTestRule .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading), + composeTestRule.activity.resources.getString(R.string.feature_foryou_loading), ) .assertExists() } @@ -96,7 +96,7 @@ class ForYouScreenTest { composeTestRule .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading), + composeTestRule.activity.resources.getString(R.string.feature_foryou_loading), ) .assertExists() } @@ -215,7 +215,7 @@ class ForYouScreenTest { composeTestRule .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading), + composeTestRule.activity.resources.getString(R.string.feature_foryou_loading), ) .assertExists() } @@ -241,7 +241,7 @@ class ForYouScreenTest { composeTestRule .onNodeWithContentDescription( - composeTestRule.activity.resources.getString(R.string.for_you_loading), + composeTestRule.activity.resources.getString(R.string.feature_foryou_loading), ) .assertExists() } diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index a24a91f1a..fb2752e08 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -216,7 +216,7 @@ internal fun ForYouScreen( targetOffsetY = { fullHeight -> -fullHeight }, ) + fadeOut(), ) { - val loadingContentDescription = stringResource(id = R.string.for_you_loading) + val loadingContentDescription = stringResource(id = R.string.feature_foryou_loading) Box( modifier = Modifier .fillMaxWidth() @@ -271,7 +271,7 @@ private fun LazyGridScope.onboarding( item(span = { GridItemSpan(maxLineSpan) }, contentType = "onboarding") { Column(modifier = interestsItemModifier) { Text( - text = stringResource(R.string.onboarding_guidance_title), + text = stringResource(R.string.feature_foryou_onboarding_guidance_title), textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() @@ -279,7 +279,7 @@ private fun LazyGridScope.onboarding( style = MaterialTheme.typography.titleMedium, ) Text( - text = stringResource(R.string.onboarding_guidance_subtitle), + text = stringResource(R.string.feature_foryou_onboarding_guidance_subtitle), modifier = Modifier .fillMaxWidth() .padding(top = 8.dp, start = 24.dp, end = 24.dp), @@ -305,7 +305,7 @@ private fun LazyGridScope.onboarding( .fillMaxWidth(), ) { Text( - text = stringResource(R.string.done), + text = stringResource(R.string.feature_foryou_done), ) } } @@ -434,7 +434,7 @@ fun TopicIcon( modifier: Modifier = Modifier, ) { DynamicAsyncImage( - placeholder = painterResource(R.drawable.ic_icon_placeholder), + placeholder = painterResource(R.drawable.feature_foryou_ic_icon_placeholder), imageUrl = imageUrl, contentDescription = null, // decorative modifier = modifier diff --git a/feature/foryou/src/main/res/drawable/ic_icon_placeholder.xml b/feature/foryou/src/main/res/drawable/feature_foryou_ic_icon_placeholder.xml similarity index 100% rename from feature/foryou/src/main/res/drawable/ic_icon_placeholder.xml rename to feature/foryou/src/main/res/drawable/feature_foryou_ic_icon_placeholder.xml diff --git a/feature/foryou/src/main/res/values/strings.xml b/feature/foryou/src/main/res/values/strings.xml index 5a33bc9c8..166749664 100644 --- a/feature/foryou/src/main/res/values/strings.xml +++ b/feature/foryou/src/main/res/values/strings.xml @@ -15,11 +15,11 @@ limitations under the License. --> - For you - Done - Loading for you… - Navigate up - What are you interested in? - Updates from topics you follow will appear here. Follow some things to get started. + For you + Done + Loading for you… + Navigate up + What are you interested in? + Updates from topics you follow will appear here. Follow some things to get started. diff --git a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt index 492e91fa3..4f9cbcc04 100644 --- a/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt +++ b/feature/interests/src/androidTest/java/com/google/samples/apps/nowinandroid/interests/InterestsScreenTest.kt @@ -50,12 +50,12 @@ class InterestsScreenTest { @Before fun setup() { composeTestRule.activity.apply { - interestsLoading = getString(R.string.loading) - interestsEmptyHeader = getString(R.string.empty_header) + interestsLoading = getString(R.string.feature_interests_loading) + interestsEmptyHeader = getString(R.string.feature_interests_empty_header) interestsTopicCardFollowButton = - getString(R.string.card_follow_button_content_desc) + getString(R.string.feature_interests_card_follow_button_content_desc) interestsTopicCardUnfollowButton = - getString(R.string.card_unfollow_button_content_desc) + getString(R.string.feature_interests_card_unfollow_button_content_desc) } } diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt index 7456ba92b..0406f4abe 100644 --- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt +++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsItem.kt @@ -68,7 +68,7 @@ fun InterestsItem( Icon( imageVector = NiaIcons.Add, contentDescription = stringResource( - id = string.card_follow_button_content_desc, + id = string.feature_interests_card_follow_button_content_desc, ), ) }, @@ -76,7 +76,7 @@ fun InterestsItem( Icon( imageVector = NiaIcons.Check, contentDescription = stringResource( - id = string.card_unfollow_button_content_desc, + id = string.feature_interests_card_unfollow_button_content_desc, ), ) }, diff --git a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt index e618c1c9f..5944b8631 100644 --- a/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt +++ b/feature/interests/src/main/java/com/google/samples/apps/nowinandroid/feature/interests/InterestsScreen.kt @@ -65,7 +65,7 @@ internal fun InterestsScreen( InterestsUiState.Loading -> NiaLoadingWheel( modifier = modifier, - contentDesc = stringResource(id = R.string.loading), + contentDesc = stringResource(id = R.string.feature_interests_loading), ) is InterestsUiState.Interests -> TopicsTabContent( @@ -82,7 +82,7 @@ internal fun InterestsScreen( @Composable private fun InterestsEmptyScreen() { - Text(text = stringResource(id = R.string.empty_header)) + Text(text = stringResource(id = R.string.feature_interests_empty_header)) } @DevicePreviews diff --git a/feature/interests/src/main/res/values/strings.xml b/feature/interests/src/main/res/values/strings.xml index 384cb1deb..2dd1c18a9 100644 --- a/feature/interests/src/main/res/values/strings.xml +++ b/feature/interests/src/main/res/values/strings.xml @@ -15,9 +15,9 @@ limitations under the License. --> - Interests - Loading data - "No available data" - Follow interest - Unfollow interest + Interests + Loading data + "No available data" + Follow interest + Unfollow interest diff --git a/feature/search/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreenTest.kt b/feature/search/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreenTest.kt index d6c07221e..8a0532e1b 100644 --- a/feature/search/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreenTest.kt +++ b/feature/search/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreenTest.kt @@ -70,17 +70,17 @@ class SearchScreenTest { @Before fun setup() { composeTestRule.activity.apply { - clearSearchContentDesc = getString(R.string.clear_search_text_content_desc) - clearRecentSearchesContentDesc = getString(R.string.clear_recent_searches_content_desc) + clearSearchContentDesc = getString(R.string.feature_search_clear_search_text_content_desc) + clearRecentSearchesContentDesc = getString(R.string.feature_search_clear_recent_searches_content_desc) followButtonContentDesc = - getString(interestsR.string.card_follow_button_content_desc) + getString(interestsR.string.feature_interests_card_follow_button_content_desc) unfollowButtonContentDesc = - getString(interestsR.string.card_unfollow_button_content_desc) - topicsString = getString(R.string.topics) - updatesString = getString(R.string.updates) - tryAnotherSearchString = getString(R.string.try_another_search) + - " " + getString(R.string.interests) + " " + getString(R.string.to_browse_topics) - searchNotReadyString = getString(R.string.search_not_ready) + getString(interestsR.string.feature_interests_card_unfollow_button_content_desc) + topicsString = getString(R.string.feature_search_topics) + updatesString = getString(R.string.feature_search_updates) + tryAnotherSearchString = getString(R.string.feature_search_try_another_search) + + " " + getString(R.string.feature_search_interests) + " " + getString(R.string.feature_search_to_browse_topics) + searchNotReadyString = getString(R.string.feature_search_not_ready) } } diff --git a/feature/search/src/main/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt b/feature/search/src/main/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt index 944d17630..7a57a1b3f 100644 --- a/feature/search/src/main/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt +++ b/feature/search/src/main/java/com/google/samples/apps/nowinandroid/feature/search/SearchScreen.kt @@ -217,7 +217,7 @@ fun EmptySearchResultBody( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(horizontal = 48.dp), ) { - val message = stringResource(id = searchR.string.search_result_not_found, searchQuery) + val message = stringResource(id = searchR.string.feature_search_result_not_found, searchQuery) val start = message.indexOf(searchQuery) Text( text = AnnotatedString( @@ -234,9 +234,9 @@ fun EmptySearchResultBody( textAlign = TextAlign.Center, modifier = Modifier.padding(vertical = 24.dp), ) - val interests = stringResource(id = searchR.string.interests) + val interests = stringResource(id = searchR.string.feature_search_interests) val tryAnotherSearchString = buildAnnotatedString { - append(stringResource(id = searchR.string.try_another_search)) + append(stringResource(id = searchR.string.feature_search_try_another_search)) append(" ") withStyle( style = SpanStyle( @@ -248,7 +248,7 @@ fun EmptySearchResultBody( append(interests) } append(" ") - append(stringResource(id = searchR.string.to_browse_topics)) + append(stringResource(id = searchR.string.feature_search_to_browse_topics)) } ClickableText( text = tryAnotherSearchString, @@ -278,7 +278,7 @@ private fun SearchNotReadyBody() { modifier = Modifier.padding(horizontal = 48.dp), ) { Text( - text = stringResource(id = searchR.string.search_not_ready), + text = stringResource(id = searchR.string.feature_search_not_ready), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center, modifier = Modifier.padding(vertical = 24.dp), @@ -321,7 +321,7 @@ private fun SearchResultBody( Text( text = buildAnnotatedString { withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(stringResource(id = searchR.string.topics)) + append(stringResource(id = searchR.string.feature_search_topics)) } }, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), @@ -360,7 +360,7 @@ private fun SearchResultBody( Text( text = buildAnnotatedString { withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(stringResource(id = searchR.string.updates)) + append(stringResource(id = searchR.string.feature_search_updates)) } }, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), @@ -412,7 +412,7 @@ private fun RecentSearchesBody( Text( text = buildAnnotatedString { withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(stringResource(id = searchR.string.recent_searches)) + append(stringResource(id = searchR.string.feature_search_recent_searches)) } }, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), @@ -427,7 +427,7 @@ private fun RecentSearchesBody( Icon( imageVector = NiaIcons.Close, contentDescription = stringResource( - id = searchR.string.clear_recent_searches_content_desc, + id = searchR.string.feature_search_clear_recent_searches_content_desc, ), tint = MaterialTheme.colorScheme.onSurface, ) @@ -465,7 +465,7 @@ private fun SearchToolbar( Icon( imageVector = NiaIcons.ArrowBack, contentDescription = stringResource( - id = string.back, + id = string.core_ui_back, ), ) } @@ -502,7 +502,7 @@ private fun SearchTextField( Icon( imageVector = NiaIcons.Search, contentDescription = stringResource( - id = searchR.string.search, + id = searchR.string.feature_search_title, ), tint = MaterialTheme.colorScheme.onSurface, ) @@ -517,7 +517,7 @@ private fun SearchTextField( Icon( imageVector = NiaIcons.Close, contentDescription = stringResource( - id = searchR.string.clear_search_text_content_desc, + id = searchR.string.feature_search_clear_search_text_content_desc, ), tint = MaterialTheme.colorScheme.onSurface, ) diff --git a/feature/search/src/main/res/values/strings.xml b/feature/search/src/main/res/values/strings.xml index 2a824653e..e11576747 100644 --- a/feature/search/src/main/res/values/strings.xml +++ b/feature/search/src/main/res/values/strings.xml @@ -15,15 +15,15 @@ limitations under the License. --> - Search - Clear search text - Sorry, there is no content found for your search \"%1$s\" - Sorry, we are still processing the search index. Please come back later. - Try another search or explorer - Interests - to browse topics - Topics - Updates - Recent searches - Clear searches + Search + Clear search text + Sorry, there is no content found for your search \"%1$s\" + Sorry, we are still processing the search index. Please come back later + Try another search or explorer + Interests + to browse topics + Topics + Updates + Recent searches + Clear searches diff --git a/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt index febc606b0..790b5964d 100644 --- a/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt +++ b/feature/settings/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialogTest.kt @@ -48,7 +48,7 @@ class SettingsDialogTest { } composeTestRule - .onNodeWithText(getString(R.string.loading)) + .onNodeWithText(getString(R.string.feature_settings_loading)) .assertExists() } @@ -71,17 +71,17 @@ class SettingsDialogTest { } // Check that all the possible settings are displayed. - composeTestRule.onNodeWithText(getString(R.string.brand_default)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_default)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_android)).assertExists() composeTestRule.onNodeWithText( - getString(R.string.dark_mode_config_system_default), + getString(R.string.feature_settings_dark_mode_config_system_default), ).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_light)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_light)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_dark)).assertExists() // Check that the correct settings are selected. - composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertIsSelected() - composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertIsSelected() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_android)).assertIsSelected() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_dark)).assertIsSelected() } @Test @@ -103,12 +103,12 @@ class SettingsDialogTest { ) } - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertExists() // Check that the correct default dynamic color setting is selected. - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertIsSelected() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertIsSelected() } @Test @@ -129,10 +129,10 @@ class SettingsDialogTest { ) } - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference)) + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference)) .assertDoesNotExist() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertDoesNotExist() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertDoesNotExist() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertDoesNotExist() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertDoesNotExist() } @Test @@ -153,10 +153,10 @@ class SettingsDialogTest { ) } - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference)) + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference)) .assertDoesNotExist() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertDoesNotExist() - composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertDoesNotExist() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertDoesNotExist() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertDoesNotExist() } @Test @@ -177,9 +177,9 @@ class SettingsDialogTest { ) } - composeTestRule.onNodeWithText(getString(R.string.privacy_policy)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.licenses)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.brand_guidelines)).assertExists() - composeTestRule.onNodeWithText(getString(R.string.feedback)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_privacy_policy)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_licenses)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_guidelines)).assertExists() + composeTestRule.onNodeWithText(getString(R.string.feature_settings_feedback)).assertExists() } } diff --git a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index 01ec30e74..c71572246 100644 --- a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -108,7 +108,7 @@ fun SettingsDialog( onDismissRequest = { onDismiss() }, title = { Text( - text = stringResource(string.settings_title), + text = stringResource(string.feature_settings_title), style = MaterialTheme.typography.titleLarge, ) }, @@ -118,7 +118,7 @@ fun SettingsDialog( when (settingsUiState) { Loading -> { Text( - text = stringResource(string.loading), + text = stringResource(string.feature_settings_loading), modifier = Modifier.padding(vertical = 16.dp), ) } @@ -140,7 +140,7 @@ fun SettingsDialog( }, confirmButton = { Text( - text = stringResource(string.dismiss_dialog_button_text), + text = stringResource(string.feature_settings_dismiss_dialog_button_text), style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary, modifier = Modifier @@ -160,50 +160,50 @@ private fun ColumnScope.SettingsPanel( onChangeDynamicColorPreference: (useDynamicColor: Boolean) -> Unit, onChangeDarkThemeConfig: (darkThemeConfig: DarkThemeConfig) -> Unit, ) { - SettingsDialogSectionTitle(text = stringResource(string.theme)) + SettingsDialogSectionTitle(text = stringResource(string.feature_settings_theme)) Column(Modifier.selectableGroup()) { SettingsDialogThemeChooserRow( - text = stringResource(string.brand_default), + text = stringResource(string.feature_settings_brand_default), selected = settings.brand == DEFAULT, onClick = { onChangeThemeBrand(DEFAULT) }, ) SettingsDialogThemeChooserRow( - text = stringResource(string.brand_android), + text = stringResource(string.feature_settings_brand_android), selected = settings.brand == ANDROID, onClick = { onChangeThemeBrand(ANDROID) }, ) } AnimatedVisibility(visible = settings.brand == DEFAULT && supportDynamicColor) { Column { - SettingsDialogSectionTitle(text = stringResource(string.dynamic_color_preference)) + SettingsDialogSectionTitle(text = stringResource(string.feature_settings_dynamic_color_preference)) Column(Modifier.selectableGroup()) { SettingsDialogThemeChooserRow( - text = stringResource(string.dynamic_color_yes), + text = stringResource(string.feature_settings_dynamic_color_yes), selected = settings.useDynamicColor, onClick = { onChangeDynamicColorPreference(true) }, ) SettingsDialogThemeChooserRow( - text = stringResource(string.dynamic_color_no), + text = stringResource(string.feature_settings_dynamic_color_no), selected = !settings.useDynamicColor, onClick = { onChangeDynamicColorPreference(false) }, ) } } } - SettingsDialogSectionTitle(text = stringResource(string.dark_mode_preference)) + SettingsDialogSectionTitle(text = stringResource(string.feature_settings_dark_mode_preference)) Column(Modifier.selectableGroup()) { SettingsDialogThemeChooserRow( - text = stringResource(string.dark_mode_config_system_default), + text = stringResource(string.feature_settings_dark_mode_config_system_default), selected = settings.darkThemeConfig == FOLLOW_SYSTEM, onClick = { onChangeDarkThemeConfig(FOLLOW_SYSTEM) }, ) SettingsDialogThemeChooserRow( - text = stringResource(string.dark_mode_config_light), + text = stringResource(string.feature_settings_dark_mode_config_light), selected = settings.darkThemeConfig == LIGHT, onClick = { onChangeDarkThemeConfig(LIGHT) }, ) SettingsDialogThemeChooserRow( - text = stringResource(string.dark_mode_config_dark), + text = stringResource(string.feature_settings_dark_mode_config_dark), selected = settings.darkThemeConfig == DARK, onClick = { onChangeDarkThemeConfig(DARK) }, ) @@ -259,7 +259,7 @@ private fun LinksPanel() { NiaTextButton( onClick = { uriHandler.openUri(PRIVACY_POLICY_URL) }, ) { - Text(text = stringResource(string.privacy_policy)) + Text(text = stringResource(string.feature_settings_privacy_policy)) } val context = LocalContext.current NiaTextButton( @@ -267,17 +267,17 @@ private fun LinksPanel() { context.startActivity(Intent(context, OssLicensesMenuActivity::class.java)) }, ) { - Text(text = stringResource(string.licenses)) + Text(text = stringResource(string.feature_settings_licenses)) } NiaTextButton( onClick = { uriHandler.openUri(BRAND_GUIDELINES_URL) }, ) { - Text(text = stringResource(string.brand_guidelines)) + Text(text = stringResource(string.feature_settings_brand_guidelines)) } NiaTextButton( onClick = { uriHandler.openUri(FEEDBACK_URL) }, ) { - Text(text = stringResource(string.feedback)) + Text(text = stringResource(string.feature_settings_feedback)) } } } diff --git a/feature/settings/src/main/res/values/strings.xml b/feature/settings/src/main/res/values/strings.xml index ad56f6b08..887539bd5 100644 --- a/feature/settings/src/main/res/values/strings.xml +++ b/feature/settings/src/main/res/values/strings.xml @@ -15,23 +15,23 @@ limitations under the License. --> - Settings - Search - Settings - Loading… - Privacy policy - Licenses - Brand Guidelines - Feedback - Theme - Default - Android - Dark mode preference - System default - Light - Dark - Use Dynamic Color - Yes - No - OK + Settings + Search + Settings + Loading… + Privacy policy + Licenses + Brand Guidelines + Feedback + Theme + Default + Android + Dark mode preference + System default + Light + Dark + Use Dynamic Color + Yes + No + OK diff --git a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt index 94f86a8e4..b64e397ea 100644 --- a/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt +++ b/feature/topic/src/androidTest/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreenTest.kt @@ -45,7 +45,7 @@ class TopicScreenTest { @Before fun setup() { composeTestRule.activity.apply { - topicLoading = getString(R.string.topic_loading) + topicLoading = getString(R.string.feature_topic_loading) } } diff --git a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt index 3dbbe7da8..3f3862c2a 100644 --- a/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt +++ b/feature/topic/src/main/java/com/google/samples/apps/nowinandroid/feature/topic/TopicScreen.kt @@ -119,7 +119,7 @@ internal fun TopicScreen( TopicUiState.Loading -> item { NiaLoadingWheel( modifier = modifier, - contentDesc = stringResource(id = string.topic_loading), + contentDesc = stringResource(id = string.feature_topic_loading), ) } @@ -284,7 +284,7 @@ private fun TopicToolbar( Icon( imageVector = NiaIcons.ArrowBack, contentDescription = stringResource( - id = com.google.samples.apps.nowinandroid.core.ui.R.string.back, + id = com.google.samples.apps.nowinandroid.core.ui.R.string.core_ui_back, ), ) } diff --git a/feature/topic/src/main/res/values/strings.xml b/feature/topic/src/main/res/values/strings.xml index 284f2f7b2..5fefc3f42 100644 --- a/feature/topic/src/main/res/values/strings.xml +++ b/feature/topic/src/main/res/values/strings.xml @@ -15,5 +15,5 @@ limitations under the License. --> - Loading topic + Loading topic diff --git a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/initializers/SyncWorkHelpers.kt b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/initializers/SyncWorkHelpers.kt index 5abf2eee5..843510aaf 100644 --- a/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/initializers/SyncWorkHelpers.kt +++ b/sync/work/src/main/java/com/google/samples/apps/nowinandroid/sync/initializers/SyncWorkHelpers.kt @@ -54,10 +54,10 @@ private fun Context.syncWorkNotification(): Notification { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( SYNC_NOTIFICATION_CHANNEL_ID, - getString(R.string.sync_notification_channel_name), + getString(R.string.sync_work_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT, ).apply { - description = getString(R.string.sync_notification_channel_description) + description = getString(R.string.sync_work_notification_channel_description) } // Register the channel with the system val notificationManager: NotificationManager? = @@ -71,9 +71,9 @@ private fun Context.syncWorkNotification(): Notification { SYNC_NOTIFICATION_CHANNEL_ID, ) .setSmallIcon( - com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification, + com.google.samples.apps.nowinandroid.core.common.R.drawable.core_common_ic_nia_notification, ) - .setContentTitle(getString(R.string.sync_notification_title)) + .setContentTitle(getString(R.string.sync_work_notification_title)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build() } diff --git a/sync/work/src/main/res/values/strings.xml b/sync/work/src/main/res/values/strings.xml index e3fd73ff8..4d77f6a7b 100644 --- a/sync/work/src/main/res/values/strings.xml +++ b/sync/work/src/main/res/values/strings.xml @@ -15,8 +15,8 @@ limitations under the License. --> - Now in Android - Sync - Background tasks for Now in Android + Now in Android + Sync + Background tasks for Now in Android From bc56ba79bc229201a6fa24c39305cb835310bfb7 Mon Sep 17 00:00:00 2001 From: kimdowoo Date: Wed, 20 Sep 2023 22:28:37 +0900 Subject: [PATCH 03/71] Add Test for Saved Top Level Destination --- .../google/samples/apps/nowinandroid/ui/NavigationTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 e1eab4796..4b8baf06b 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 @@ -166,7 +166,10 @@ class NavigationTest { composeTestRule.apply { // GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown. onNodeWithContentDescription(navigateUp).assertDoesNotExist() - // TODO: Add top level destinations here, see b/226357686. + + onNodeWithText(saved).performClick() + onNodeWithContentDescription(navigateUp).assertDoesNotExist() + onNodeWithText(interests).performClick() onNodeWithContentDescription(navigateUp).assertDoesNotExist() } From 1c78acf73b797602bc8a5fedb647310f17d49ca9 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 12 Aug 2023 00:44:56 +0200 Subject: [PATCH 04/71] Enable dependabot updates on a daily basis using the `version update` label for gradle updates. --- .github/dependabot.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..6a9528470 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + registries: "*" + labels: [ "version update" ] +registries: + maven-google: + type: "maven-repository" + url: "https://maven.google.com" + replaces-base: true From 8826c9cb424b4051e6448bcf99312d7cdb8d00e6 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 24 Aug 2023 18:19:22 +0200 Subject: [PATCH 05/71] Schedule weekly instead of daily as suggested --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6a9528470..5096f3bfb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,11 +4,11 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" - package-ecosystem: "gradle" directory: "/" schedule: - interval: "daily" + interval: "weekly" registries: "*" labels: [ "version update" ] registries: From 8d5984e8432680733a2e02a05e74ef6417b0cebc Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 24 Aug 2023 18:25:16 +0200 Subject: [PATCH 06/71] Add "Kotlin - KSP - Compose" update group --- .github/dependabot.yml | 7 +++++++ gradle/libs.versions.toml | 1 + 2 files changed, 8 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5096f3bfb..00ada2a1c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,13 @@ updates: interval: "weekly" registries: "*" labels: [ "version update" ] + groups: + kotlin-ksp-compose: + patterns: + - "org.jetbrains.kotlin:*" + - "org.jetbrains.kotlin.jvm" + - "com.google.devtools.ksp" + - "androidx.compose.compiler:compiler" registries: maven-google: type: "maven-repository" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 08391f2e6..f8d908c02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,6 +64,7 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidxBrowser" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } +androidx-compose-compiler = { group = "androidx.compose.compiler", name = "compiler", version.ref = "androidxComposeCompiler" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } From 7133de7faaa7b09aa7e832f61ecd30a1660b84b7 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 24 Aug 2023 18:27:27 +0200 Subject: [PATCH 07/71] Remove `.github/renovate.json` --- .github/renovate.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/renovate.json diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index f19341761..000000000 --- a/.github/renovate.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", "group:all", ":dependencyDashboard", "schedule:daily" - ], - "packageRules": [ - { - "matchPackageNames": ["org.objenesis:objenesis"], - "allowedVersions": "<=2.6" - }, - { - "matchPackageNames": ["com.google.protobuf"], - "allowedVersions": "<=0.8.19" - } - ] -} From caa482bc71faa2f20ddb1e1f0fdba92e9a1aef2a Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 16 Nov 2023 23:45:31 +0100 Subject: [PATCH 08/71] Kotlinify codebase - Remove unnecessary nullable types - Replace no-op method bodies with Unit - Convert to expression body - Replace if with when - Remove braces from 'when' entries - Remove braces from if statement - Convert to single line lambda - oneline if/returns - Replace 'contains' call with 'in' operator Following this refactor, it could be great to envision a more "strict" code formatter like ktlint 1.0 (we are currently stuck at 0.48.1) --- .../apps/nowinandroid/ui/NavigationTest.kt | 2 +- .../samples/apps/nowinandroid/MainActivity.kt | 4 +- .../apps/nowinandroid/di/JankStatsModule.kt | 23 +++--- .../samples/apps/nowinandroid/ui/NiaApp.kt | 4 +- .../apps/nowinandroid/ui/NiaAppState.kt | 4 +- .../test/uiautomator/UiAutomatorHelpers.kt | 14 ++-- .../baselineprofile/StartupBaselineProfile.kt | 13 ++-- .../apps/nowinandroid/PrintTestApks.kt | 6 +- .../apps/nowinandroid/core/result/Result.kt | 13 ++-- .../DefaultRecentSearchRepository.kt | 4 +- .../fake/FakeRecentSearchRepository.kt | 4 +- .../fake/FakeSearchContentsRepository.kt | 2 +- .../repository/fake/FakeTopicsRepository.kt | 5 +- ...CompositeUserNewsResourceRepositoryTest.kt | 4 +- .../core/data/UserNewsResourceTest.kt | 4 +- .../data/testdoubles/TestNewsResourceDao.kt | 6 +- .../testdoubles/TestNiaNetworkDataSource.kt | 11 ++- .../core/data/testdoubles/TestTopicDao.kt | 21 ++---- .../core/datastore/ListToMapMigration.kt | 5 +- .../datastore/NiaPreferencesDataSource.kt | 8 +-- .../core/designsystem/ThemeTest.kt | 70 +++++++------------ .../component/DynamicAsyncImage.kt | 3 +- .../core/designsystem/component/TopAppBar.kt | 4 +- .../component/scrollbar/ScrollbarExt.kt | 9 +-- .../core/designsystem/theme/Tint.kt | 2 +- .../core/domain/GetFollowableTopicsUseCase.kt | 28 ++++---- .../core/model/data/UserNewsResource.kt | 11 ++- .../core/notifications/SystemTrayNotifier.kt | 40 +++++------ .../core/testing/NiaTestRunner.kt | 5 +- .../testing/repository/TestNewsRepository.kt | 4 +- .../repository/TestRecentSearchRepository.kt | 4 +- .../TestSearchContentsRepository.kt | 9 +-- .../repository/TestTopicsRepository.kt | 5 +- .../core/testing/util/MainDispatcherRule.kt | 8 +-- .../core/testing/util/TestAnalyticsHelper.kt | 2 +- .../core/testing/util/TestSyncManager.kt | 4 +- .../core/ui/JankStatsExtensions.kt | 4 +- .../apps/nowinandroid/core/ui/NewsFeed.kt | 4 +- .../feature/bookmarks/BookmarksScreen.kt | 3 +- .../navigation/BookmarksNavigation.kt | 2 +- .../foryou/navigation/ForYouNavigation.kt | 2 +- .../navigation/InterestsNavigation.kt | 2 +- .../feature/search/SearchScreen.kt | 8 +-- .../feature/topic/TopicViewModel.kt | 33 ++------- .../topic/navigation/TopicNavigation.kt | 2 +- .../lint/designsystem/DesignSystemDetector.kt | 15 ++-- 46 files changed, 161 insertions(+), 279 deletions(-) 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 5861cda58..d57556ac2 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 @@ -90,7 +90,7 @@ class NavigationTest { lateinit var topicsRepository: TopicsRepository private fun AndroidComposeTestRule<*, *>.stringResource(@StringRes resId: Int) = - ReadOnlyProperty { _, _ -> activity.getString(resId) } + ReadOnlyProperty { _, _ -> activity.getString(resId) } // The strings used for matching in these tests private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 7fe1bc674..0d03aa300 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -90,9 +90,7 @@ class MainActivity : ComponentActivity() { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState - .onEach { - uiState = it - } + .onEach { uiState = it } .collect() } } diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt index be64d057f..56d1b6e24 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt @@ -20,6 +20,7 @@ import android.app.Activity import android.util.Log import android.view.Window import androidx.metrics.performance.JankStats +import androidx.metrics.performance.JankStats.OnFrameListener import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -29,26 +30,20 @@ import dagger.hilt.android.components.ActivityComponent @InstallIn(ActivityComponent::class) object JankStatsModule { @Provides - fun providesOnFrameListener(): JankStats.OnFrameListener { - return JankStats.OnFrameListener { frameData -> - // Make sure to only log janky frames. - if (frameData.isJank) { - // We're currently logging this but would better report it to a backend. - Log.v("NiA Jank", frameData.toString()) - } + fun providesOnFrameListener(): OnFrameListener = OnFrameListener { frameData -> + // Make sure to only log janky frames. + if (frameData.isJank) { + // We're currently logging this but would better report it to a backend. + Log.v("NiA Jank", frameData.toString()) } } @Provides - fun providesWindow(activity: Activity): Window { - return activity.window - } + fun providesWindow(activity: Activity): Window = activity.window @Provides fun providesJankStats( window: Window, - frameListener: JankStats.OnFrameListener, - ): JankStats { - return JankStats.createAndTrack(window, frameListener) - } + frameListener: OnFrameListener, + ): JankStats = JankStats.createAndTrack(window, frameListener) } 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 aa85afebd..caefeab4d 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 @@ -97,9 +97,7 @@ fun NiaApp( ) { val shouldShowGradientBackground = appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU - var showSettingsDialog by rememberSaveable { - mutableStateOf(false) - } + var showSettingsDialog by rememberSaveable { mutableStateOf(false) } NiaBackground { NiaGradientBackground( 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 09e70069e..c652d08f1 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 @@ -164,9 +164,7 @@ class NiaAppState( } } - fun navigateToSearch() { - navController.navigateToSearch() - } + fun navigateToSearch() = navController.navigateToSearch() } /** diff --git a/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt b/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt index 85867b982..b0eb754c7 100644 --- a/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt +++ b/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt @@ -29,15 +29,11 @@ import androidx.test.uiautomator.HasChildrenOp.EXACTLY fun untilHasChildren( childCount: Int = 1, op: HasChildrenOp = AT_LEAST, -): UiObject2Condition { - return object : UiObject2Condition() { - override fun apply(element: UiObject2): Boolean { - return when (op) { - AT_LEAST -> element.childCount >= childCount - EXACTLY -> element.childCount == childCount - AT_MOST -> element.childCount <= childCount - } - } +): UiObject2Condition = object : UiObject2Condition() { + override fun apply(element: UiObject2): Boolean = when (op) { + AT_LEAST -> element.childCount >= childCount + EXACTLY -> element.childCount == childCount + AT_MOST -> element.childCount <= childCount } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt index c5a88e1bd..d8128a670 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt @@ -16,6 +16,7 @@ package com.google.samples.apps.nowinandroid.baselineprofile +import androidx.benchmark.macro.MacrobenchmarkScope import androidx.benchmark.macro.junit4.BaselineProfileRule import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications @@ -30,11 +31,9 @@ class StartupBaselineProfile { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test - fun generate() = - baselineProfileRule.collect( - PACKAGE_NAME, - includeInStartupProfile = true, - ) { - startActivityAndAllowNotifications() - } + fun generate() = baselineProfileRule.collect( + PACKAGE_NAME, + includeInStartupProfile = true, + profileBlock = MacrobenchmarkScope::startActivityAndAllowNotifications, + ) } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt index 6c08216cc..4928072fc 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt @@ -79,14 +79,12 @@ internal abstract class PrintApkLocationTask : DefaultTask() { fun taskAction() { val hasFiles = sources.orNull?.any { directory -> directory.asFileTree.files.any { - it.isFile && it.parentFile.path.contains("build${File.separator}generated").not() + it.isFile && "build${File.separator}generated" !in it.parentFile.path } } ?: throw RuntimeException("Cannot check androidTest sources") // Don't print APK location if there are no androidTest source files - if (!hasFiles) { - return - } + if (!hasFiles) return val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get()) ?: throw RuntimeException("Cannot load APKs") diff --git a/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/result/Result.kt b/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/result/Result.kt index 6ae12d634..22376d082 100644 --- a/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/result/Result.kt +++ b/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/result/Result.kt @@ -23,15 +23,10 @@ import kotlinx.coroutines.flow.onStart sealed interface Result { data class Success(val data: T) : Result - data class Error(val exception: Throwable? = null) : Result + data class Error(val exception: Throwable) : Result data object Loading : Result } -fun Flow.asResult(): Flow> { - return this - .map> { - Result.Success(it) - } - .onStart { emit(Result.Loading) } - .catch { emit(Result.Error(it)) } -} +fun Flow.asResult(): Flow> = map> { Result.Success(it) } + .onStart { emit(Result.Loading) } + .catch { emit(Result.Error(it)) } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt index 702c2dcd2..ca7e62853 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt @@ -39,9 +39,7 @@ class DefaultRecentSearchRepository @Inject constructor( override fun getRecentSearchQueries(limit: Int): Flow> = recentSearchQueryDao.getRecentSearchQueryEntities(limit).map { searchQueries -> - searchQueries.map { - it.asExternalModel() - } + searchQueries.map { it.asExternalModel() } } override suspend fun clearRecentSearches() = recentSearchQueryDao.clearRecentSearchQueries() diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt index fc649f3ec..025b51f68 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeRecentSearchRepository.kt @@ -26,10 +26,10 @@ import javax.inject.Inject * Fake implementation of the [RecentSearchRepository] */ class FakeRecentSearchRepository @Inject constructor() : RecentSearchRepository { - override suspend fun insertOrReplaceRecentSearch(searchQuery: String) { /* no-op */ } + override suspend fun insertOrReplaceRecentSearch(searchQuery: String) = Unit override fun getRecentSearchQueries(limit: Int): Flow> = flowOf(emptyList()) - override suspend fun clearRecentSearches() { /* no-op */ } + override suspend fun clearRecentSearches() = Unit } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt index d15890a10..65cced452 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeSearchContentsRepository.kt @@ -27,7 +27,7 @@ import javax.inject.Inject */ class FakeSearchContentsRepository @Inject constructor() : SearchContentsRepository { - override suspend fun populateFtsData() { /* no-op */ } + override suspend fun populateFtsData() = Unit override fun searchContents(searchQuery: String): Flow = flowOf() override fun getSearchContentsCount(): Flow = flowOf(1) } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt index 1ab9c9353..0eefc8451 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/fake/FakeTopicsRepository.kt @@ -55,9 +55,8 @@ class FakeTopicsRepository @Inject constructor( ) }.flowOn(ioDispatcher) - override fun getTopic(id: String): Flow { - return getTopics().map { it.first { topic -> topic.id == id } } - } + override fun getTopic(id: String): Flow = getTopics() + .map { it.first { topic -> topic.id == id } } override suspend fun syncWith(synchronizer: Synchronizer) = true } diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt index 743fb7e5c..05811f4be 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt @@ -82,7 +82,7 @@ class CompositeUserNewsResourceRepositoryTest { // Check that only news resources with the given topic id are returned. assertEquals( sampleNewsResources - .filter { it.topics.contains(sampleTopic1) } + .filter { sampleTopic1 in it.topics } .mapToUserNewsResources(emptyUserData), userNewsResources.first(), ) @@ -104,7 +104,7 @@ class CompositeUserNewsResourceRepositoryTest { // Check that only news resources with the given topic id are returned. assertEquals( sampleNewsResources - .filter { it.topics.contains(sampleTopic1) } + .filter { sampleTopic1 in it.topics } .mapToUserNewsResources(userData), userNewsResources.first(), ) diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt index a21dee863..c7dfd99d0 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt @@ -91,14 +91,14 @@ class UserNewsResourceTest { // Construct the expected FollowableTopic. val followableTopic = FollowableTopic( topic = topic, - isFollowed = userData.followedTopics.contains(topic.id), + isFollowed = topic.id in userData.followedTopics, ) assertTrue(userNewsResource.followableTopics.contains(followableTopic)) } // Check that the saved flag is set correctly. assertEquals( - userData.bookmarkedNewsResources.contains(newsResource1.id), + newsResource1.id in userData.bookmarkedNewsResources, userNewsResource.isSaved, ) } 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 6e5c45305..dc4b78e01 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 @@ -34,9 +34,7 @@ val nonPresentInterestsIds = setOf("2") */ class TestNewsResourceDao : NewsResourceDao { - private var entitiesStateFlow = MutableStateFlow( - emptyList(), - ) + private val entitiesStateFlow = MutableStateFlow(emptyList()) internal var topicCrossReferences: List = listOf() @@ -131,7 +129,7 @@ class TestNewsResourceDao : NewsResourceDao { override suspend fun deleteNewsResources(ids: List) { val idSet = ids.toSet() entitiesStateFlow.update { entities -> - entities.filterNot { idSet.contains(it.id) } + entities.filterNot { it.id in idSet } } } } diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNiaNetworkDataSource.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNiaNetworkDataSource.kt index 8e248a3aa..7f9a69959 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNiaNetworkDataSource.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNiaNetworkDataSource.kt @@ -91,11 +91,10 @@ class TestNiaNetworkDataSource : NiaNetworkDataSource { } } -fun List.after(version: Int?): List = - when (version) { - null -> this - else -> this.filter { it.changeListVersion > version } - } +fun List.after(version: Int?): List = when (version) { + null -> this + else -> filter { it.changeListVersion > version } +} /** * Return items from [this] whose id defined by [idGetter] is in [ids] if [ids] is not null @@ -105,7 +104,7 @@ private fun List.matchIds( idGetter: (T) -> String, ) = when (ids) { null -> this - else -> ids.toSet().let { idSet -> this.filter { idSet.contains(idGetter(it)) } } + else -> ids.toSet().let { idSet -> filter { idGetter(it) in idSet } } } /** diff --git a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestTopicDao.kt b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestTopicDao.kt index a52cc86f6..d217f55d7 100644 --- a/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestTopicDao.kt +++ b/core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestTopicDao.kt @@ -28,20 +28,15 @@ import kotlinx.coroutines.flow.update */ class TestTopicDao : TopicDao { - private var entitiesStateFlow = MutableStateFlow( - emptyList(), - ) + private val entitiesStateFlow = MutableStateFlow(emptyList()) - override fun getTopicEntity(topicId: String): Flow { + override fun getTopicEntity(topicId: String): Flow = throw NotImplementedError("Unused in tests") - } - override fun getTopicEntities(): Flow> = - entitiesStateFlow + override fun getTopicEntities(): Flow> = entitiesStateFlow override fun getTopicEntities(ids: Set): Flow> = - getTopicEntities() - .map { topics -> topics.filter { it.id in ids } } + getTopicEntities().map { topics -> topics.filter { it.id in ids } } override suspend fun getOneOffTopicEntities(): List = emptyList() @@ -55,15 +50,11 @@ class TestTopicDao : TopicDao { override suspend fun upsertTopics(entities: List) { // Overwrite old values with new values - entitiesStateFlow.update { oldValues -> - (entities + oldValues).distinctBy(TopicEntity::id) - } + entitiesStateFlow.update { oldValues -> (entities + oldValues).distinctBy(TopicEntity::id) } } override suspend fun deleteTopics(ids: List) { val idSet = ids.toSet() - entitiesStateFlow.update { entities -> - entities.filterNot { idSet.contains(it.id) } - } + entitiesStateFlow.update { entities -> entities.filterNot { it.id in idSet } } } } diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt index 5bff23340..c1e49a44b 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt @@ -52,7 +52,6 @@ object ListToMapMigration : DataMigration { hasDoneListToMapMigration = true } - override suspend fun shouldMigrate(currentData: UserPreferences): Boolean { - return !currentData.hasDoneListToMapMigration - } + override suspend fun shouldMigrate(currentData: UserPreferences): Boolean = + !currentData.hasDoneListToMapMigration } diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt index 6dc7725c1..9a76a75a1 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/NiaPreferencesDataSource.kt @@ -103,9 +103,7 @@ class NiaPreferencesDataSource @Inject constructor( suspend fun setDynamicColorPreference(useDynamicColor: Boolean) { userPreferences.updateData { - it.copy { - this.useDynamicColor = useDynamicColor - } + it.copy { this.useDynamicColor = useDynamicColor } } } @@ -190,9 +188,7 @@ class NiaPreferencesDataSource @Inject constructor( suspend fun setShouldHideOnboarding(shouldHideOnboarding: Boolean) { userPreferences.updateData { - it.copy { - this.shouldHideOnboarding = shouldHideOnboarding - } + it.copy { this.shouldHideOnboarding = shouldHideOnboarding } } } } diff --git a/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt b/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt index b10df053c..92e94f4e5 100644 --- a/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt +++ b/core/designsystem/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/ThemeTest.kt @@ -16,7 +16,8 @@ package com.google.samples.apps.nowinandroid.core.designsystem -import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.dynamicDarkColorScheme @@ -219,60 +220,41 @@ class ThemeTest { } @Composable - private fun dynamicLightColorSchemeWithFallback(): ColorScheme { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - dynamicLightColorScheme(LocalContext.current) - } else { - LightDefaultColorScheme - } + private fun dynamicLightColorSchemeWithFallback(): ColorScheme = when { + SDK_INT >= VERSION_CODES.S -> dynamicLightColorScheme(LocalContext.current) + else -> LightDefaultColorScheme } @Composable - private fun dynamicDarkColorSchemeWithFallback(): ColorScheme { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - dynamicDarkColorScheme(LocalContext.current) - } else { - DarkDefaultColorScheme - } + private fun dynamicDarkColorSchemeWithFallback(): ColorScheme = when { + SDK_INT >= VERSION_CODES.S -> dynamicDarkColorScheme(LocalContext.current) + else -> DarkDefaultColorScheme } - private fun emptyGradientColors(colorScheme: ColorScheme): GradientColors { - return GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp)) - } + private fun emptyGradientColors(colorScheme: ColorScheme): GradientColors = + GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp)) - private fun defaultGradientColors(colorScheme: ColorScheme): GradientColors { - return GradientColors( - top = colorScheme.inverseOnSurface, - bottom = colorScheme.primaryContainer, - container = colorScheme.surface, - ) - } + private fun defaultGradientColors(colorScheme: ColorScheme): GradientColors = GradientColors( + top = colorScheme.inverseOnSurface, + bottom = colorScheme.primaryContainer, + container = colorScheme.surface, + ) - private fun dynamicGradientColorsWithFallback(colorScheme: ColorScheme): GradientColors { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - emptyGradientColors(colorScheme) - } else { - defaultGradientColors(colorScheme) - } + private fun dynamicGradientColorsWithFallback(colorScheme: ColorScheme): GradientColors = when { + SDK_INT >= VERSION_CODES.S -> emptyGradientColors(colorScheme) + else -> defaultGradientColors(colorScheme) } - private fun defaultBackgroundTheme(colorScheme: ColorScheme): BackgroundTheme { - return BackgroundTheme( - color = colorScheme.surface, - tonalElevation = 2.dp, - ) - } + private fun defaultBackgroundTheme(colorScheme: ColorScheme): BackgroundTheme = BackgroundTheme( + color = colorScheme.surface, + tonalElevation = 2.dp, + ) - private fun defaultTintTheme(): TintTheme { - return TintTheme() - } + private fun defaultTintTheme(): TintTheme = TintTheme() - private fun dynamicTintThemeWithFallback(colorScheme: ColorScheme): TintTheme { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - TintTheme(colorScheme.primary) - } else { - TintTheme() - } + private fun dynamicTintThemeWithFallback(colorScheme: ColorScheme): TintTheme = when { + SDK_INT >= VERSION_CODES.S -> TintTheme(colorScheme.primary) + else -> TintTheme() } /** diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt index cc352107b..1092ba4c9 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/DynamicAsyncImage.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color.Companion.Unspecified import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale @@ -79,7 +80,7 @@ fun DynamicAsyncImage( contentScale = ContentScale.Crop, painter = if (isError.not() && !isLocalInspection) imageLoader else placeholder, contentDescription = contentDescription, - colorFilter = if (iconTint != null) ColorFilter.tint(iconTint) else null, + colorFilter = if (iconTint != Unspecified) ColorFilter.tint(iconTint) else null, ) } } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt index 99f935d2a..9c716918a 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/TopAppBar.kt @@ -40,9 +40,9 @@ import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons fun NiaTopAppBar( @StringRes titleRes: Int, navigationIcon: ImageVector, - navigationIconContentDescription: String?, + navigationIconContentDescription: String, actionIcon: ImageVector, - actionIconContentDescription: String?, + actionIconContentDescription: String, modifier: Modifier = Modifier, colors: TopAppBarColors = TopAppBarDefaults.centerAlignedTopAppBarColors(), onNavigationClick: () -> Unit = {}, diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt index 7a0282bf7..0984317b9 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt @@ -229,10 +229,5 @@ fun LazyStaggeredGridState.scrollbarState( .collect { value = it } }.value -private inline fun List.floatSumOf(selector: (T) -> Float): Float { - var sum = 0f - for (element in this) { - sum += selector(element) - } - return sum -} +private inline fun List.floatSumOf(selector: (T) -> Float): Float = + fold(0f) { acc, it -> acc + selector(it) } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/theme/Tint.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/theme/Tint.kt index 848c8d8f5..75ab3a8f6 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/theme/Tint.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/theme/Tint.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.graphics.Color */ @Immutable data class TintTheme( - val iconTint: Color? = null, + val iconTint: Color = Color.Unspecified, ) /** diff --git a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt index c3c045d44..0167a3192 100644 --- a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt +++ b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetFollowableTopicsUseCase.kt @@ -37,22 +37,20 @@ class GetFollowableTopicsUseCase @Inject constructor( * * @param sortBy - the field used to sort the topics. Default NONE = no sorting. */ - operator fun invoke(sortBy: TopicSortField = NONE): Flow> { - return combine( - userDataRepository.userData, - topicsRepository.getTopics(), - ) { userData, topics -> - val followedTopics = topics - .map { topic -> - FollowableTopic( - topic = topic, - isFollowed = topic.id in userData.followedTopics, - ) - } - when (sortBy) { - NAME -> followedTopics.sortedBy { it.topic.name } - else -> followedTopics + operator fun invoke(sortBy: TopicSortField = NONE): Flow> = combine( + userDataRepository.userData, + topicsRepository.getTopics(), + ) { userData, topics -> + val followedTopics = topics + .map { topic -> + FollowableTopic( + topic = topic, + isFollowed = topic.id in userData.followedTopics, + ) } + when (sortBy) { + NAME -> followedTopics.sortedBy { it.topic.name } + else -> followedTopics } } } 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 4ea830022..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 @@ -45,14 +45,13 @@ data class UserNewsResource internal constructor( followableTopics = newsResource.topics.map { topic -> FollowableTopic( topic = topic, - isFollowed = userData.followedTopics.contains(topic.id), + isFollowed = topic.id in userData.followedTopics, ) }, - isSaved = userData.bookmarkedNewsResources.contains(newsResource.id), - hasBeenViewed = userData.viewedNewsResources.contains(newsResource.id), + isSaved = newsResource.id in userData.bookmarkedNewsResources, + hasBeenViewed = newsResource.id in userData.viewedNewsResources, ) } -fun List.mapToUserNewsResources(userData: UserData): List { - return map { UserNewsResource(it, userData) } -} +fun List.mapToUserNewsResources(userData: UserData): List = + map { UserNewsResource(it, userData) } diff --git a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt index 7e74f819c..fbc60baf1 100644 --- a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt +++ b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt @@ -24,10 +24,10 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Build.VERSION import android.os.Build.VERSION_CODES -import androidx.core.app.ActivityCompat +import androidx.core.app.ActivityCompat.checkSelfPermission import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.InboxStyle import androidx.core.app.NotificationManagerCompat @@ -57,30 +57,24 @@ class SystemTrayNotifier @Inject constructor( override fun postNewsNotifications( newsResources: List, ) = with(context) { - if (ActivityCompat.checkSelfPermission( - this, - permission.POST_NOTIFICATIONS, - ) != PackageManager.PERMISSION_GRANTED - ) { + if (checkSelfPermission(this, permission.POST_NOTIFICATIONS) != PERMISSION_GRANTED) { return } - val truncatedNewsResources = newsResources - .take(MAX_NUM_NOTIFICATIONS) + val truncatedNewsResources = newsResources.take(MAX_NUM_NOTIFICATIONS) - val newsNotifications = truncatedNewsResources - .map { newsResource -> - createNewsNotification { - setSmallIcon( - com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification, - ) - .setContentTitle(newsResource.title) - .setContentText(newsResource.content) - .setContentIntent(newsPendingIntent(newsResource)) - .setGroup(NEWS_NOTIFICATION_GROUP) - .setAutoCancel(true) - } + val newsNotifications = truncatedNewsResources.map { newsResource -> + createNewsNotification { + setSmallIcon( + com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification, + ) + .setContentTitle(newsResource.title) + .setContentText(newsResource.content) + .setContentIntent(newsPendingIntent(newsResource)) + .setGroup(NEWS_NOTIFICATION_GROUP) + .setAutoCancel(true) } + } val summaryNotification = createNewsNotification { val title = getString( R.string.news_notification_group_summary, @@ -117,9 +111,7 @@ class SystemTrayNotifier @Inject constructor( newsResources: List, title: String, ): InboxStyle = newsResources - .fold(InboxStyle()) { inboxStyle, newsResource -> - inboxStyle.addLine(newsResource.title) - } + .fold(InboxStyle()) { inboxStyle, newsResource -> inboxStyle.addLine(newsResource.title) } .setBigContentTitle(title) .setSummaryText(title) } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/NiaTestRunner.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/NiaTestRunner.kt index 30254a617..9b3b185df 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/NiaTestRunner.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/NiaTestRunner.kt @@ -25,7 +25,6 @@ import dagger.hilt.android.testing.HiltTestApplication * A custom runner to set up the instrumented application class for tests. */ class NiaTestRunner : AndroidJUnitRunner() { - override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { - return super.newApplication(cl, HiltTestApplication::class.java.name, context) - } + override fun newApplication(cl: ClassLoader, name: String, context: Context): Application = + super.newApplication(cl, HiltTestApplication::class.java.name, context) } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestNewsRepository.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestNewsRepository.kt index d0bfd21a1..ef065a9f8 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestNewsRepository.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestNewsRepository.kt @@ -43,9 +43,7 @@ class TestNewsRepository : NewsRepository { } } query.filterNewsIds?.let { filterNewsIds -> - result = newsResources.filter { - filterNewsIds.contains(it.id) - } + result = newsResources.filter { it.id in filterNewsIds } } result } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestRecentSearchRepository.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestRecentSearchRepository.kt index 961473401..f700bdc31 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestRecentSearchRepository.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestRecentSearchRepository.kt @@ -32,7 +32,5 @@ class TestRecentSearchRepository : RecentSearchRepository { cachedRecentSearches.add(RecentSearchQuery(searchQuery)) } - override suspend fun clearRecentSearches() { - cachedRecentSearches.clear() - } + override suspend fun clearRecentSearches() = cachedRecentSearches.clear() } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt index 2aa54e463..9b6151449 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestSearchContentsRepository.kt @@ -29,18 +29,15 @@ class TestSearchContentsRepository : SearchContentsRepository { private val cachedTopics: MutableList = mutableListOf() private val cachedNewsResources: MutableList = mutableListOf() - override suspend fun populateFtsData() { /* no-op */ } + override suspend fun populateFtsData() = Unit override fun searchContents(searchQuery: String): Flow = flowOf( SearchResult( topics = cachedTopics.filter { - it.name.contains(searchQuery) || - it.shortDescription.contains(searchQuery) || - it.longDescription.contains(searchQuery) + searchQuery in it.name || searchQuery in it.shortDescription || searchQuery in it.longDescription }, newsResources = cachedNewsResources.filter { - it.content.contains(searchQuery) || - it.title.contains(searchQuery) + searchQuery in it.content || searchQuery in it.title }, ), ) diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestTopicsRepository.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestTopicsRepository.kt index a95469d83..ddccbbe35 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestTopicsRepository.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/repository/TestTopicsRepository.kt @@ -33,9 +33,8 @@ class TestTopicsRepository : TopicsRepository { override fun getTopics(): Flow> = topicsFlow - override fun getTopic(id: String): Flow { - return topicsFlow.map { topics -> topics.find { it.id == id }!! } - } + override fun getTopic(id: String): Flow = + topicsFlow.map { topics -> topics.find { it.id == id }!! } /** * A test-only API to allow controlling the list of topics from tests. diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt index 28155f5ad..666c4edd4 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/MainDispatcherRule.kt @@ -32,11 +32,7 @@ import org.junit.runner.Description class MainDispatcherRule( private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), ) : TestWatcher() { - override fun starting(description: Description) { - Dispatchers.setMain(testDispatcher) - } + override fun starting(description: Description) = Dispatchers.setMain(testDispatcher) - override fun finished(description: Description) { - Dispatchers.resetMain() - } + override fun finished(description: Description) = Dispatchers.resetMain() } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestAnalyticsHelper.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestAnalyticsHelper.kt index 005784c21..5f72d30e6 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestAnalyticsHelper.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestAnalyticsHelper.kt @@ -26,5 +26,5 @@ class TestAnalyticsHelper : AnalyticsHelper { events.add(event) } - fun hasLogged(event: AnalyticsEvent) = events.contains(event) + fun hasLogged(event: AnalyticsEvent) = event in events } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestSyncManager.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestSyncManager.kt index 999b67195..ff1e2fdd9 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestSyncManager.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/TestSyncManager.kt @@ -26,9 +26,7 @@ class TestSyncManager : SyncManager { override val isSyncing: Flow = syncStatusFlow - override fun requestSync() { - TODO("Not yet implemented") - } + override fun requestSync(): Unit = TODO("Not yet implemented") /** * A test-only API to set the sync status from tests. diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/JankStatsExtensions.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/JankStatsExtensions.kt index c9fee1ac8..ef3de1059 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/JankStatsExtensions.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/JankStatsExtensions.kt @@ -50,7 +50,7 @@ fun rememberMetricsStateHolder(): Holder { */ @Composable fun TrackJank( - vararg keys: Any?, + vararg keys: Any, reportMetric: suspend CoroutineScope.(state: Holder) -> Unit, ) { val metrics = rememberMetricsStateHolder() @@ -65,7 +65,7 @@ fun TrackJank( */ @Composable fun TrackDisposableJank( - vararg keys: Any?, + vararg keys: Any, reportMetric: DisposableEffectScope.(state: Holder) -> DisposableEffectResult, ) { val metrics = rememberMetricsStateHolder() 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 e2904afc3..7264c7b14 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 @@ -64,9 +64,7 @@ fun LazyStaggeredGridScope.newsFeed( key = { it.id }, contentType = { "newsFeedItem" }, ) { userNewsResource -> - val resourceUrl by remember { - mutableStateOf(Uri.parse(userNewsResource.url)) - } + val resourceUrl by remember { mutableStateOf(Uri.parse(userNewsResource.url)) } val context = LocalContext.current val analyticsHelper = LocalAnalyticsHelper.current val backgroundColor = MaterialTheme.colorScheme.background.toArgb() diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt index 7d51c6e84..1fa429d21 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/BookmarksScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.testTag @@ -237,7 +238,7 @@ private fun EmptyState(modifier: Modifier = Modifier) { Image( modifier = Modifier.fillMaxWidth(), painter = painterResource(id = R.drawable.img_empty_bookmarks), - colorFilter = if (iconTint != null) ColorFilter.tint(iconTint) else null, + colorFilter = if (iconTint != Color.Unspecified) ColorFilter.tint(iconTint) else null, contentDescription = null, ) diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt index ebcde4ab1..aa8237da4 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt @@ -24,7 +24,7 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute const val bookmarksRoute = "bookmarks_route" -fun NavController.navigateToBookmarks(navOptions: NavOptions? = null) { +fun NavController.navigateToBookmarks(navOptions: NavOptions) { this.navigate(bookmarksRoute, navOptions) } diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index 705495cc2..ca606161d 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -30,7 +30,7 @@ const val forYouNavigationRoute = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" private const val DEEP_LINK_URI_PATTERN = "https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}" -fun NavController.navigateToForYou(navOptions: NavOptions? = null) { +fun NavController.navigateToForYou(navOptions: NavOptions) { this.navigate(forYouNavigationRoute, navOptions) } 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 7558ec48d..3bc0ad770 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 @@ -26,7 +26,7 @@ import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute private const val INTERESTS_GRAPH_ROUTE_PATTERN = "interests_graph" const val interestsRoute = "interests_route" -fun NavController.navigateToInterestsGraph(navOptions: NavOptions? = null) { +fun NavController.navigateToInterestsGraph(navOptions: NavOptions) { this.navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions) } 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 65b65f61d..b6e977571 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 @@ -264,9 +264,7 @@ fun EmptySearchResultBody( ) { offset -> tryAnotherSearchString.getStringAnnotations(start = offset, end = offset) .firstOrNull() - ?.let { - onInterestsClick() - } + ?.let { onInterestsClick() } } } } @@ -519,9 +517,7 @@ private fun SearchTextField( } }, onValueChange = { - if (!it.contains("\n")) { - onSearchQueryChanged(it) - } + if ("\n" !in it) onSearchQueryChanged(it) }, modifier = Modifier .fillMaxWidth() diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt index 6adfe0a67..ff1eee319 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModel.kt @@ -117,22 +117,16 @@ private fun topicUiState( when (followedTopicToTopicResult) { is Result.Success -> { val (followedTopics, topic) = followedTopicToTopicResult.data - val followed = followedTopics.contains(topicId) TopicUiState.Success( followableTopic = FollowableTopic( topic = topic, - isFollowed = followed, + isFollowed = topicId in followedTopics, ), ) } - is Result.Loading -> { - TopicUiState.Loading - } - - is Result.Error -> { - TopicUiState.Error - } + is Result.Loading -> TopicUiState.Loading + is Result.Error -> TopicUiState.Error } } } @@ -151,26 +145,13 @@ private fun newsUiState( val bookmark: Flow> = userDataRepository.userData .map { it.bookmarkedNewsResources } - return combine( - newsStream, - bookmark, - ::Pair, - ) + return combine(newsStream, bookmark, ::Pair) .asResult() .map { newsToBookmarksResult -> when (newsToBookmarksResult) { - is Result.Success -> { - val news = newsToBookmarksResult.data.first - NewsUiState.Success(news) - } - - is Result.Loading -> { - NewsUiState.Loading - } - - is Result.Error -> { - NewsUiState.Error - } + is Result.Success -> NewsUiState.Success(newsToBookmarksResult.data.first) + is Result.Loading -> NewsUiState.Loading + is Result.Error -> NewsUiState.Error } } } 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 c29b57d47..2d75a8d8d 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 @@ -40,7 +40,7 @@ internal class TopicArgs(val topicId: String) { fun NavController.navigateToTopic(topicId: String) { val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) - this.navigate("topic_route/$encodedId") { + navigate("topic_route/$encodedId") { launchSingleTop = true } } diff --git a/lint/src/main/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetector.kt b/lint/src/main/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetector.kt index 4c9d55764..09af17db9 100644 --- a/lint/src/main/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetector.kt +++ b/lint/src/main/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetector.kt @@ -34,15 +34,13 @@ import org.jetbrains.uast.UQualifiedReferenceExpression */ class DesignSystemDetector : Detector(), Detector.UastScanner { - override fun getApplicableUastTypes(): List> { - return listOf( - UCallExpression::class.java, - UQualifiedReferenceExpression::class.java, - ) - } + override fun getApplicableUastTypes(): List> = listOf( + UCallExpression::class.java, + UQualifiedReferenceExpression::class.java, + ) - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { + override fun createUastHandler(context: JavaContext): UElementHandler = + object : UElementHandler() { override fun visitCallExpression(node: UCallExpression) { val name = node.methodName ?: return val preferredName = METHOD_NAMES[name] ?: return @@ -55,7 +53,6 @@ class DesignSystemDetector : Detector(), Detector.UastScanner { reportIssue(context, node, name, preferredName) } } - } companion object { @JvmField From 743ec8c08121a005ce6f591d3a34bb9b78795024 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Wed, 29 Nov 2023 16:17:30 +0100 Subject: [PATCH 09/71] Update Gradle 8.5 https://docs.gradle.org/8.5/release-notes.html --- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f862f..1af9e0930 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 3142ecb4955c82f7443eb6f40ed384f5877cb384 Mon Sep 17 00:00:00 2001 From: Ben Trengrove Date: Tue, 5 Dec 2023 11:03:55 +1100 Subject: [PATCH 10/71] Remove redundant mutable state --- .../google/samples/apps/nowinandroid/core/ui/NewsFeed.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 e2904afc3..afdb584a2 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 @@ -30,9 +30,6 @@ import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext @@ -64,9 +61,6 @@ fun LazyStaggeredGridScope.newsFeed( key = { it.id }, contentType = { "newsFeedItem" }, ) { userNewsResource -> - val resourceUrl by remember { - mutableStateOf(Uri.parse(userNewsResource.url)) - } val context = LocalContext.current val analyticsHelper = LocalAnalyticsHelper.current val backgroundColor = MaterialTheme.colorScheme.background.toArgb() @@ -79,7 +73,8 @@ fun LazyStaggeredGridScope.newsFeed( analyticsHelper.logNewsResourceOpened( newsResourceId = userNewsResource.id, ) - launchCustomChromeTab(context, resourceUrl, backgroundColor) + launchCustomChromeTab(context, Uri.parse(userNewsResource.url), backgroundColor) + onNewsResourceViewed(userNewsResource.id) }, hasBeenViewed = userNewsResource.hasBeenViewed, From e334a727c35c3dc28a8bab954e822450fedbda5d Mon Sep 17 00:00:00 2001 From: dahunsi Date: Mon, 4 Dec 2023 16:13:11 -0800 Subject: [PATCH 11/71] Fix typo in app scrollbars Change-Id: Ie72f1410fa8e5a68a520321c34d4fad251016486 --- .../core/designsystem/component/scrollbar/AppScrollbars.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5b6776352..fe14bce14 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 @@ -222,7 +222,7 @@ private fun scrollbarThumbColor( val pressed by interactionSource.collectIsPressedAsState() val hovered by interactionSource.collectIsHoveredAsState() val dragged by interactionSource.collectIsDraggedAsState() - val active = (scrollableState.canScrollForward || scrollableState.canScrollForward) && + val active = (scrollableState.canScrollForward || scrollableState.canScrollBackward) && (pressed || hovered || dragged || scrollableState.isScrollInProgress) val color = animateColorAsState( From a96e0709d83d0df6c3432018661dcd204354be51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:06:44 +0000 Subject: [PATCH 12/71] Bump actions/setup-java from 3 to 4 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Build.yaml | 4 ++-- .github/workflows/Release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index ca161b0a4..9c103ca46 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -31,7 +31,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 @@ -128,7 +128,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml index f85215b74..f4901b9e2 100644 --- a/.github/workflows/Release.yml +++ b/.github/workflows/Release.yml @@ -21,7 +21,7 @@ jobs: run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 From af26182193a4752c0c7a64ef2636166f3d8a04a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:10:32 +0000 Subject: [PATCH 13/71] Bump hilt from 2.48.1 to 2.49 Bumps `hilt` from 2.48.1 to 2.49. Updates `com.google.dagger:hilt-android` from 2.48.1 to 2.49 - [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.48.1...dagger-2.49) Updates `com.google.dagger:hilt-android-testing` from 2.48.1 to 2.49 - [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.48.1...dagger-2.49) Updates `com.google.dagger:hilt-android-compiler` from 2.48.1 to 2.49 - [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.48.1...dagger-2.49) Updates `com.google.dagger.hilt.android` from 2.48.1 to 2.49 - [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.48.1...dagger-2.49) --- 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 aa2a32e90..c1f426bd3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,7 +38,7 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -hilt = "2.48.1" +hilt = "2.49" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" From b80e8a5e3f851e62ebe021a75bc8f5697b1f9c54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:10:36 +0000 Subject: [PATCH 14/71] Bump org.jetbrains.kotlinx:kotlinx-datetime from 0.4.1 to 0.5.0 Bumps [org.jetbrains.kotlinx:kotlinx-datetime](https://github.com/Kotlin/kotlinx-datetime) from 0.4.1 to 0.5.0. - [Release notes](https://github.com/Kotlin/kotlinx-datetime/releases) - [Changelog](https://github.com/Kotlin/kotlinx-datetime/blob/master/CHANGELOG.md) - [Commits](https://github.com/Kotlin/kotlinx-datetime/compare/v0.4.1...v0.5.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx:kotlinx-datetime 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 aa2a32e90..41c92c2f2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ jacoco = "0.8.7" junit4 = "4.13.2" kotlin = "1.9.10" kotlinxCoroutines = "1.7.3" -kotlinxDatetime = "0.4.1" +kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" ksp = "1.9.10-1.0.13" lint = "31.1.3" From b75ddce6308dc71df28bae69d1da26761bc34152 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:10:38 +0000 Subject: [PATCH 15/71] Bump androidx.compose.runtime:runtime-tracing Bumps androidx.compose.runtime:runtime-tracing from 1.0.0-alpha03 to 1.0.0-beta01. --- updated-dependencies: - dependency-name: androidx.compose.runtime:runtime-tracing dependency-type: direct:production update-type: version-update:semver-patch ... 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 aa2a32e90..d265e2a73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" androidxComposeBom = "2023.10.01" androidxComposeCompiler = "1.5.3" -androidxComposeRuntimeTracing = "1.0.0-alpha03" +androidxComposeRuntimeTracing = "1.0.0-beta01" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.0.0" From 9a8bfd86eda6c3e8f5b93ddf37ff6b2ef6617ad9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 23:10:43 +0000 Subject: [PATCH 16/71] Bump androidxMacroBenchmark from 1.2.0 to 1.2.2 Bumps `androidxMacroBenchmark` from 1.2.0 to 1.2.2. Updates `androidx.benchmark:benchmark-macro-junit4` from 1.2.0 to 1.2.2 Updates `androidx.baselineprofile` from 1.2.0 to 1.2.2 --- updated-dependencies: - dependency-name: androidx.benchmark:benchmark-macro-junit4 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.baselineprofile dependency-type: direct:production update-type: version-update:semver-patch ... 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 aa2a32e90..a82684d1a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ androidxEspresso = "3.5.1" androidxHiltNavigationCompose = "1.0.0" androidxJunit = "1.1.5" androidxLifecycle = "2.6.2" -androidxMacroBenchmark = "1.2.0" +androidxMacroBenchmark = "1.2.2" androidxMetrics = "1.0.0-alpha04" androidxNavigation = "2.7.4" androidxProfileinstaller = "1.3.1" From 07d987733108db806f6fecfc90341cc34e1859b6 Mon Sep 17 00:00:00 2001 From: Yuya <140081005+oikvpqya@users.noreply.github.com> Date: Wed, 6 Dec 2023 09:59:00 +0900 Subject: [PATCH 17/71] Update thumb movement range by thumb size, in app scrollbars Change-Id: I4438debd5037965a837758e0c78b110ab8197da5 --- .../component/scrollbar/Scrollbar.kt | 33 ++++++++++++------- .../component/scrollbar/ThumbExt.kt | 3 +- 2 files changed, 23 insertions(+), 13 deletions(-) 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 5041eff7b..1e5850f30 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 @@ -91,6 +91,12 @@ class ScrollbarState { */ val thumbMovedPercent get() = unpackFloat2(packedValue) + + /** + * Returns the max distance the thumb can travel as a percentage of total track size + */ + val thumbTrackSizePercent + get() = 1f - unpackFloat1(packedValue) } /** @@ -310,27 +316,30 @@ fun Scrollbar( b = minThumbSize.toPx(), ) - val thumbTravelPercent = when { - interactionThumbTravelPercent.isNaN() -> state.thumbMovedPercent - else -> interactionThumbTravelPercent + val trackSizePx = when (state.thumbTrackSizePercent) { + 0f -> track.size + else -> (track.size - thumbSizePx) / state.thumbTrackSizePercent } - val thumbMovedPx = min( - a = track.size * thumbTravelPercent, - b = track.size - thumbSizePx, + val thumbTravelPercent = max( + a = min( + a = when { + interactionThumbTravelPercent.isNaN() -> state.thumbMovedPercent + else -> interactionThumbTravelPercent + }, + b = state.thumbTrackSizePercent, + ), + b = 0f, ) - val scrollbarThumbMovedPx = max( - a = thumbMovedPx.roundToInt(), - b = 0, - ) + val thumbMovedPx = trackSizePx * thumbTravelPercent val y = when (orientation) { Horizontal -> 0 - Vertical -> scrollbarThumbMovedPx + Vertical -> thumbMovedPx.roundToInt() } val x = when (orientation) { - Horizontal -> scrollbarThumbMovedPx + Horizontal -> thumbMovedPx.roundToInt() Vertical -> 0 } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt index 847580361..a267ec2ec 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ThumbExt.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue +import kotlin.math.roundToInt /** * Remembers a function to react to [Scrollbar] thumb position displacements for a [LazyListState] @@ -79,7 +80,7 @@ private inline fun rememberDraggableScroller( LaunchedEffect(percentage) { if (percentage.isNaN()) return@LaunchedEffect - val indexToFind = (itemCount * percentage).toInt() + val indexToFind = (itemCount * percentage).roundToInt() scroll(indexToFind) } return remember { From 8a992996a4a59c721d5257d8b8fe2661e552ebec Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Wed, 6 Dec 2023 14:56:38 -0800 Subject: [PATCH 18/71] Enable stricter validation for build-logic convention plugins Change-Id: I1955393c76e91bebfe4b7e349734c81a10263c8c --- .github/workflows/Build.yaml | 3 +++ build-logic/convention/build.gradle.kts | 7 +++++++ .../com/google/samples/apps/nowinandroid/Badging.kt | 9 +++++++++ .../google/samples/apps/nowinandroid/PrintTestApks.kt | 7 +++++++ 4 files changed, 26 insertions(+) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index ca161b0a4..d47342c6a 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -39,6 +39,9 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Check build-logic + run: ./gradlew check -p build-logic + - name: Check spotless run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index b6650aa75..e75ed468f 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -43,6 +43,13 @@ dependencies { compileOnly(libs.ksp.gradlePlugin) } +tasks { + validatePlugins { + enableStricterValidation.set(true) + failOnWarning.set(true) + } +} + gradlePlugin { plugins { register("androidApplicationCompose") { diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt index bcaa00f9f..2a776e429 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt @@ -26,11 +26,14 @@ import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.register @@ -40,14 +43,17 @@ import java.io.File import java.nio.file.Files import javax.inject.Inject +@CacheableTask abstract class GenerateBadgingTask : DefaultTask() { @get:OutputFile abstract val badging: RegularFileProperty + @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val apk: RegularFileProperty + @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val aapt2Executable: RegularFileProperty @@ -68,6 +74,7 @@ abstract class GenerateBadgingTask : DefaultTask() { } } +@CacheableTask abstract class CheckBadgingTask : DefaultTask() { // In order for the task to be up-to-date when the inputs have not changed, @@ -76,9 +83,11 @@ abstract class CheckBadgingTask : DefaultTask() { @get:OutputDirectory abstract val output: DirectoryProperty + @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val goldenBadging: RegularFileProperty + @get:PathSensitive(PathSensitivity.NONE) @get:InputFile abstract val generatedBadging: RegularFileProperty diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt index 6c08216cc..94bf6e127 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt @@ -30,7 +30,10 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault import java.io.File internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtension<*, *, *>) { @@ -62,10 +65,14 @@ internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtensio } } +@DisableCachingByDefault(because = "Prints output") internal abstract class PrintApkLocationTask : DefaultTask() { + + @get:PathSensitive(PathSensitivity.RELATIVE) @get:InputDirectory abstract val apkFolder: DirectoryProperty + @get:PathSensitive(PathSensitivity.RELATIVE) @get:InputFiles abstract val sources: ListProperty From 46b0a8108097bb2cf949e19c5caf094e51eb8d4e Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Thu, 7 Dec 2023 12:19:06 -0800 Subject: [PATCH 19/71] Use normal propery setting Change-Id: I31aef2007b63c067ad80a9f65f2b9594d114f13a --- build-logic/convention/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index e75ed468f..ea57a7c8c 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -45,8 +45,8 @@ dependencies { tasks { validatePlugins { - enableStricterValidation.set(true) - failOnWarning.set(true) + enableStricterValidation = true + failOnWarning = true } } From e8f4537e7c526a4a10e7e9cb9d1f31910c013179 Mon Sep 17 00:00:00 2001 From: Yuya <140081005+oikvpqya@users.noreply.github.com> Date: Fri, 8 Dec 2023 09:11:28 +0900 Subject: [PATCH 20/71] Update thumbTrackSizePercent calculation for readability Change-Id: I7ef067a950b4b5911b8c2e6a8c1f72c3e95d938d --- .../core/designsystem/component/scrollbar/Scrollbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e5850f30..8c85e5be5 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 @@ -96,7 +96,7 @@ class ScrollbarState { * Returns the max distance the thumb can travel as a percentage of total track size */ val thumbTrackSizePercent - get() = 1f - unpackFloat1(packedValue) + get() = 1f - thumbSizePercent } /** From 5df9cce37cf8a5df494fe1772802d21ba6c13147 Mon Sep 17 00:00:00 2001 From: Alex Vanyo Date: Fri, 8 Dec 2023 11:32:01 -0800 Subject: [PATCH 21/71] Add truth for better badging diff output Change-Id: Id3fab0107bc08adf44ddf07a30e0327e06897d47 --- build-logic/convention/build.gradle.kts | 1 + .../samples/apps/nowinandroid/Badging.kt | 20 +++++++------------ gradle/libs.versions.toml | 2 ++ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index ea57a7c8c..e11a1b42a 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { compileOnly(libs.firebase.performance.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.ksp.gradlePlugin) + implementation(libs.truth) } tasks { diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt index 2a776e429..c59d3ffb8 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt @@ -20,8 +20,8 @@ import com.android.build.api.artifact.SingleArtifact import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.BaseExtension import com.android.SdkConstants +import com.google.common.truth.Truth.assertWithMessage import org.gradle.api.DefaultTask -import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty @@ -40,7 +40,6 @@ import org.gradle.kotlin.dsl.register import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.process.ExecOperations import java.io.File -import java.nio.file.Files import javax.inject.Inject @CacheableTask @@ -98,17 +97,12 @@ abstract class CheckBadgingTask : DefaultTask() { @TaskAction fun taskAction() { - if ( - Files.mismatch( - goldenBadging.get().asFile.toPath(), - generatedBadging.get().asFile.toPath(), - ) != -1L - ) { - throw GradleException( - "Generated badging is different from golden badging! " + - "If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}", - ) - } + assertWithMessage( + "Generated badging is different from golden badging! " + + "If this change is intended, run ./gradlew ${updateBadgingTaskName.get()}", + ) + .that(generatedBadging.get().asFile.readText()) + .isEqualTo(goldenBadging.get().asFile.readText()) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa2a32e90..4f55e6290 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,7 @@ robolectric = "4.11.1" roborazzi = "1.6.0" room = "2.6.0" secrets = "2.0.1" +truth = "1.1.5" turbine = "1.0.0" [libraries] @@ -140,6 +141,7 @@ roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", versi room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } # Dependencies of the included build-logic From 8d0a9bacd48e5a35f9124f2848dcc74dac59e470 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 10:25:22 +0100 Subject: [PATCH 22/71] Update max concurrent PRs for dependabot We currently have many pending updates that are not listed and the default 5 concurrent PRs is very limiting. See documentation on https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#open-pull-requests-limit --- .github/dependabot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 00ada2a1c..d77a706b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,7 @@ updates: - "org.jetbrains.kotlin.jvm" - "com.google.devtools.ksp" - "androidx.compose.compiler:compiler" + open-pull-requests-limit: 10 registries: maven-google: type: "maven-repository" From 02adbe0aada8da1448c1661e8c19743e29980afa Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 09:29:17 +0000 Subject: [PATCH 23/71] Remove unnecessary FlowPreview opt-in --- .../kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt | 1 - 1 file changed, 1 deletion(-) 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 04ee4e56e..d7cadf8d7 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 @@ -86,7 +86,6 @@ private fun Project.configureKotlin() { "-opt-in=kotlin.RequiresOptIn", // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview", ) } } From e305b07271278eba15d7e3dc4bc4a184aa28cceb Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 09:32:00 +0000 Subject: [PATCH 24/71] Remove unnecessary RequiresOptIn opt-in --- .../kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt | 1 - 1 file changed, 1 deletion(-) 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 04ee4e56e..b453cf722 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 @@ -83,7 +83,6 @@ private fun Project.configureKotlin() { val warningsAsErrors: String? by project allWarningsAsErrors = warningsAsErrors.toBoolean() freeCompilerArgs = freeCompilerArgs + listOf( - "-opt-in=kotlin.RequiresOptIn", // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.FlowPreview", From 5f7d4ea1d57cf497ff830875ae6d2fa0fcc7f7ee Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 11:28:12 +0100 Subject: [PATCH 25/71] Replace custom `RoomSchemaArgProvider` in favor of the new Room Gradle plugin > Since Room 2.6.0, a Gradle plugin was released to solve various existing issues in Room regarding having inputs and outputs of schemas via Gradle annotation processor options. > Changelog: https://developer.android.com/jetpack/androidx/releases/room#2.6.0-alpha02 See https://github.com/gradle/android-cache-fix-gradle-plugin/issues/544 for reference. --- build-logic/convention/build.gradle.kts | 1 + .../kotlin/AndroidRoomConventionPlugin.kt | 24 ++++--------------- build.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts index b6650aa75..d9f825294 100644 --- a/build-logic/convention/build.gradle.kts +++ b/build-logic/convention/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { compileOnly(libs.firebase.performance.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.ksp.gradlePlugin) + compileOnly(libs.room.gradlePlugin) } gradlePlugin { diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index b67fb1b26..29d31f9e6 100644 --- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -14,29 +14,25 @@ * limitations under the License. */ -import com.google.devtools.ksp.gradle.KspExtension +import androidx.room.gradle.RoomExtension import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.process.CommandLineArgumentProvider -import java.io.File class AndroidRoomConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { + pluginManager.apply("androidx.room") pluginManager.apply("com.google.devtools.ksp") - extensions.configure { + extensions.configure { // The schemas directory contains a schema file for each version of the Room database. // This is required to enable Room auto migrations. // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration. - arg(RoomSchemaArgProvider(File(projectDir, "schemas"))) + schemaDirectory("$projectDir/schemas") } dependencies { @@ -46,16 +42,4 @@ class AndroidRoomConventionPlugin : Plugin { } } } - - /** - * https://issuetracker.google.com/issues/132245929 - * [Export schemas](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas) - */ - class RoomSchemaArgProvider( - @get:InputDirectory - @get:PathSensitive(PathSensitivity.RELATIVE) - val schemaDir: File, - ) : CommandLineArgumentProvider { - override fun asArguments() = listOf("room.schemaLocation=${schemaDir.path}") - } } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7b54f9058..d6c076b06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,4 +43,5 @@ plugins { alias(libs.plugins.ksp) apply false alias(libs.plugins.roborazzi) apply false alias(libs.plugins.secrets) apply false + alias(libs.plugins.room) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1eb9116e8..dc08a2fef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -148,6 +148,7 @@ firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "fir firebase-performance-gradlePlugin = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } +room-gradlePlugin = { group = "androidx.room", name = "room-gradle-plugin", version.ref = "room" } work-testing = { group = "androidx.work", name = "work-testing", version = "2.8.1" } [plugins] @@ -164,6 +165,7 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } +room = { id = "androidx.room", version.ref = "room" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" } # Plugins defined by this project From f27a2e176e539c4c9fb00ec941bec1aef426137f Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 17:42:45 +0100 Subject: [PATCH 26/71] Reduce overall build complexity - Reduce the visibility of multiple Hilt `Module`s and implementations of public interfaces - Correctly configure the visibility of dependencies: - `api` when it's part of it's public `api` - `implementation` when it's part of it's internal `implementation` - Remove unnecessary dependencies in build.gradle.kts files - Remove unnecessary dependencies provided by plugins - Remove unnecessary applied plugins (i.e. roborazzi) - Sort dependencies in `build.gradle.kts` - Delete unused entries in `libs.versions.toml` --- app-nia-catalog/build.gradle.kts | 3 +- app/build.gradle.kts | 43 ++++++++----------- ...droidApplicationComposeConventionPlugin.kt | 2 - .../kotlin/AndroidFeatureConventionPlugin.kt | 16 ------- .../kotlin/AndroidHiltConventionPlugin.kt | 2 - .../AndroidLibraryComposeConventionPlugin.kt | 2 - .../kotlin/AndroidLibraryConventionPlugin.kt | 3 -- .../apps/nowinandroid/AndroidCompose.kt | 5 --- build.gradle.kts | 1 + core/analytics/build.gradle.kts | 7 ++- .../core/analytics/AnalyticsModule.kt | 2 +- .../core/analytics/StubAnalyticsHelper.kt | 2 +- .../core/analytics/AnalyticsModule.kt | 2 +- .../core/analytics/FirebaseAnalyticsHelper.kt | 2 +- core/common/build.gradle.kts | 4 +- .../core/network/di/CoroutineScopesModule.kt | 2 +- core/data-test/build.gradle.kts | 4 +- core/data/build.gradle.kts | 16 +++---- .../nowinandroid/core/data/di/DataModule.kt | 14 +++--- .../di/UserNewsResourceRepositoryModule.kt | 2 +- .../data/repository/AnalyticsExtensions.kt | 12 +++--- .../DefaultRecentSearchRepository.kt | 2 +- .../DefaultSearchContentsRepository.kt | 2 +- .../repository/OfflineFirstNewsRepository.kt | 2 +- .../OfflineFirstTopicsRepository.kt | 2 +- .../OfflineFirstUserDataRepository.kt | 2 +- .../util/ConnectivityManagerNetworkMonitor.kt | 2 +- core/database/build.gradle.kts | 3 +- .../nowinandroid/core/database/DaosModule.kt | 2 +- .../core/database/DatabaseMigrations.kt | 2 +- .../core/database/DatabaseModule.kt | 2 +- .../nowinandroid/core/database/NiaDatabase.kt | 2 +- .../core/database/util/InstantConverter.kt | 2 +- core/datastore-proto/build.gradle.kts | 2 +- core/datastore-test/build.gradle.kts | 7 +-- .../datastore/test/TestDataStoreModule.kt | 2 +- core/datastore/build.gradle.kts | 9 ++-- .../core/datastore/IntToStringIdsMigration.kt | 2 +- .../core/datastore/ListToMapMigration.kt | 2 +- .../core/datastore/di/DataStoreModule.kt | 2 +- core/designsystem/build.gradle.kts | 10 ++++- core/domain/build.gradle.kts | 9 ++-- core/model/build.gradle.kts | 2 +- core/network/build.gradle.kts | 10 ++--- .../core/network/di/FlavoredNetworkModule.kt | 2 +- .../core/network/di/NetworkModule.kt | 2 +- .../network/retrofit/RetrofitNiaNetwork.kt | 2 +- .../core/network/di/FlavoredNetworkModule.kt | 2 +- core/notifications/build.gradle.kts | 13 ++---- .../core/notifications/NotificationsModule.kt | 2 +- .../core/notifications/NoOpNotifier.kt | 2 +- .../core/notifications/SystemTrayNotifier.kt | 2 +- .../core/notifications/NotificationsModule.kt | 2 +- core/testing/build.gradle.kts | 29 ++++++------- .../core/testing/di/TestDispatcherModule.kt | 2 +- .../core/testing/di/TestDispatchersModule.kt | 2 +- core/ui/build.gradle.kts | 21 ++------- feature/bookmarks/build.gradle.kts | 6 ++- feature/foryou/build.gradle.kts | 10 ++++- feature/interests/build.gradle.kts | 9 ++++ feature/search/build.gradle.kts | 7 ++- feature/settings/build.gradle.kts | 9 ++-- feature/topic/build.gradle.kts | 6 ++- gradle/libs.versions.toml | 15 +------ sync/sync-test/build.gradle.kts | 4 +- .../core/sync/test/NeverSyncingSyncManager.kt | 2 +- .../core/sync/test/TestSyncModule.kt | 2 +- sync/work/build.gradle.kts | 20 ++++----- .../apps/nowinandroid/sync/di/SyncModule.kt | 6 +-- .../sync/status/StubSyncSubscriber.kt | 2 +- .../sync/status/WorkManagerSyncManager.kt | 2 +- .../sync/workers/AnalyticsExtensions.kt | 4 +- .../nowinandroid/sync/workers/SyncWorker.kt | 2 +- .../apps/nowinandroid/sync/di/SyncModule.kt | 8 ++-- .../sync/services/SyncNotificationsService.kt | 2 +- .../sync/status/FirebaseSyncSubscriber.kt | 2 +- 76 files changed, 191 insertions(+), 238 deletions(-) diff --git a/app-nia-catalog/build.gradle.kts b/app-nia-catalog/build.gradle.kts index e02f6bc0b..0991ea0c5 100644 --- a/app-nia-catalog/build.gradle.kts +++ b/app-nia-catalog/build.gradle.kts @@ -65,9 +65,10 @@ android { } dependencies { + implementation(libs.androidx.activity.compose) + implementation(projects.core.designsystem) implementation(projects.core.ui) - implementation(libs.androidx.activity.compose) } dependencyGuard { diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b003c4a54..38166f7ff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,6 +25,7 @@ plugins { alias(libs.plugins.nowinandroid.android.application.firebase) id("com.google.android.gms.oss-licenses-plugin") alias(libs.plugins.baselineprofile) + alias(libs.plugins.roborazzi) } android { @@ -96,47 +97,41 @@ dependencies { implementation(projects.core.data) implementation(projects.core.model) implementation(projects.core.analytics) - implementation(projects.sync.work) - androidTestImplementation(projects.core.testing) - androidTestImplementation(projects.core.datastoreTest) - androidTestImplementation(projects.core.dataTest) - androidTestImplementation(projects.core.network) - androidTestImplementation(libs.androidx.navigation.testing) - androidTestImplementation(libs.accompanist.testharness) - androidTestImplementation(kotlin("test")) - debugImplementation(libs.androidx.compose.ui.testManifest) - debugImplementation(projects.uiTestHiltManifest) - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.compose.runtime) + implementation(libs.androidx.tracing.ktx) implementation(libs.androidx.lifecycle.runtimeCompose) - implementation(libs.androidx.compose.runtime.tracing) implementation(libs.androidx.compose.material3.windowSizeClass) - implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.window.manager) implementation(libs.androidx.profileinstaller) implementation(libs.kotlinx.coroutines.guava) implementation(libs.coil.kt) - baselineProfile(project(":benchmarks")) + debugImplementation(libs.androidx.compose.ui.testManifest) + debugImplementation(projects.uiTestHiltManifest) + + kspTest(libs.hilt.compiler) - // Core functions - testImplementation(projects.core.testing) - testImplementation(projects.core.datastoreTest) testImplementation(projects.core.dataTest) - testImplementation(projects.core.network) - testImplementation(libs.androidx.navigation.testing) + testImplementation(projects.core.testing) testImplementation(libs.accompanist.testharness) + testImplementation(libs.hilt.android.testing) testImplementation(libs.work.testing) - testImplementation(kotlin("test")) - kspTest(libs.hilt.compiler) + testDemoImplementation(libs.robolectric) + testDemoImplementation(libs.roborazzi) + + androidTestImplementation(projects.core.testing) + androidTestImplementation(projects.core.dataTest) + androidTestImplementation(projects.core.datastoreTest) + androidTestImplementation(libs.androidx.navigation.testing) + androidTestImplementation(libs.accompanist.testharness) + androidTestImplementation(libs.hilt.android.testing) + + baselineProfile(projects.benchmarks) } baselineProfile { diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt index bb79715e4..3eeed97cf 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt @@ -24,8 +24,6 @@ class AndroidApplicationComposeConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.application") - // Screenshot Tests - pluginManager.apply("io.github.takahirom.roborazzi") val extension = extensions.getByType() configureAndroidCompose(extension) diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index cc42d60fd..7a334beb3 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -21,7 +21,6 @@ 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.kotlin class AndroidFeatureConventionPlugin : Plugin { override fun apply(target: Project) { @@ -39,27 +38,12 @@ class AndroidFeatureConventionPlugin : Plugin { } dependencies { - add("implementation", project(":core:model")) add("implementation", project(":core:ui")) add("implementation", project(":core:designsystem")) - add("implementation", project(":core:data")) - add("implementation", project(":core:common")) - add("implementation", project(":core:domain")) - add("implementation", project(":core:analytics")) - - add("testImplementation", kotlin("test")) - add("testImplementation", project(":core:testing")) - add("androidTestImplementation", kotlin("test")) - add("androidTestImplementation", project(":core:testing")) - - add("implementation", libs.findLibrary("coil.kt").get()) - add("implementation", libs.findLibrary("coil.kt.compose").get()) add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) - - add("implementation", libs.findLibrary("kotlinx.coroutines.android").get()) } } } diff --git a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt index b24594570..fcb4f823e 100644 --- a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -30,8 +30,6 @@ class AndroidHiltConventionPlugin : Plugin { dependencies { "implementation"(libs.findLibrary("hilt.android").get()) "ksp"(libs.findLibrary("hilt.compiler").get()) - "kspAndroidTest"(libs.findLibrary("hilt.compiler").get()) - "kspTest"(libs.findLibrary("hilt.compiler").get()) } } diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt index 05f442354..dd9eead63 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt @@ -26,8 +26,6 @@ class AndroidLibraryComposeConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.library") - // Screenshot Tests - pluginManager.apply("io.github.takahirom.roborazzi") val extension = extensions.getByType() configureAndroidCompose(extension) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index ef84cfbb4..c4d4a8fc4 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -48,9 +48,6 @@ class AndroidLibraryConventionPlugin : Plugin { } dependencies { add("testImplementation", kotlin("test")) - add("testImplementation", project(":core:testing")) - add("androidTestImplementation", kotlin("test")) - add("androidTestImplementation", project(":core:testing")) } } } 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 614d4f2d0..039987cac 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,11 +41,6 @@ internal fun Project.configureAndroidCompose( val bom = libs.findLibrary("androidx-compose-bom").get() add("implementation", platform(bom)) add("androidTestImplementation", platform(bom)) - // Add ComponentActivity to debug manifest - add("debugImplementation", libs.findLibrary("androidx.compose.ui.testManifest").get()) - // Screenshot Tests on JVM - add("testImplementation", libs.findLibrary("robolectric").get()) - add("testImplementation", libs.findLibrary("roborazzi").get()) } testOptions { diff --git a/build.gradle.kts b/build.gradle.kts index a2c39f493..647c8f5d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,7 @@ buildscript { // Lists all plugins used throughout the project without applying them. plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false alias(libs.plugins.android.test) apply false alias(libs.plugins.baselineprofile) apply false alias(libs.plugins.kotlin.jvm) apply false diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index 0f712085c..023574e6f 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -24,9 +24,8 @@ android { } dependencies { - implementation(platform(libs.firebase.bom)) implementation(libs.androidx.compose.runtime) - implementation(libs.androidx.core.ktx) - implementation(libs.firebase.analytics) - implementation(libs.kotlinx.coroutines.android) + + prodImplementation(platform(libs.firebase.bom)) + prodImplementation(libs.firebase.analytics) } diff --git a/core/analytics/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt b/core/analytics/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt index 78ebec9e5..4ad6b6dc2 100644 --- a/core/analytics/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt +++ b/core/analytics/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt @@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -abstract class AnalyticsModule { +internal abstract class AnalyticsModule { @Binds abstract fun bindsAnalyticsHelper(analyticsHelperImpl: StubAnalyticsHelper): AnalyticsHelper } diff --git a/core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt b/core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt index 2ff022287..f570be4a9 100644 --- a/core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt +++ b/core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt @@ -27,7 +27,7 @@ private const val TAG = "StubAnalyticsHelper" * analytics events should be sent to a backend. */ @Singleton -class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper { +internal class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper { override fun logEvent(event: AnalyticsEvent) { Log.d(TAG, "Received analytics event: $event") } diff --git a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt index 9f875ae6d..957b987d9 100644 --- a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt +++ b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt @@ -28,7 +28,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -abstract class AnalyticsModule { +internal abstract class AnalyticsModule { @Binds abstract fun bindsAnalyticsHelper(analyticsHelperImpl: FirebaseAnalyticsHelper): AnalyticsHelper diff --git a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt index 75dfbc468..5a4b7f362 100644 --- a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt +++ b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt @@ -23,7 +23,7 @@ import javax.inject.Inject /** * Implementation of `AnalyticsHelper` which logs events to a Firebase backend. */ -class FirebaseAnalyticsHelper @Inject constructor( +internal class FirebaseAnalyticsHelper @Inject constructor( private val firebaseAnalytics: FirebaseAnalytics, ) : AnalyticsHelper { diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index d539d1892..51ae627dc 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -24,6 +24,6 @@ android { } dependencies { - implementation(libs.kotlinx.coroutines.android) - testImplementation(projects.core.testing) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.turbine) } \ No newline at end of file diff --git a/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt b/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt index c265394a8..6e7ca6bb3 100644 --- a/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt +++ b/core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/CoroutineScopesModule.kt @@ -34,7 +34,7 @@ annotation class ApplicationScope @Module @InstallIn(SingletonComponent::class) -object CoroutineScopesModule { +internal object CoroutineScopesModule { @Provides @Singleton @ApplicationScope diff --git a/core/data-test/build.gradle.kts b/core/data-test/build.gradle.kts index 7ca3ecd0d..b166df288 100644 --- a/core/data-test/build.gradle.kts +++ b/core/data-test/build.gradle.kts @@ -24,6 +24,6 @@ android { dependencies { api(projects.core.data) - implementation(projects.core.testing) - implementation(projects.core.common) + + implementation(libs.hilt.android.testing) } diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index e730a9eb3..142637ff9 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -31,18 +31,16 @@ android { } dependencies { + api(projects.core.common) + api(projects.core.database) + api(projects.core.datastore) + api(projects.core.network) + implementation(projects.core.analytics) - implementation(projects.core.common) - implementation(projects.core.database) - implementation(projects.core.datastore) - implementation(projects.core.model) - implementation(projects.core.network) implementation(projects.core.notifications) - implementation(libs.androidx.core.ktx) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.serialization.json) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.kotlinx.serialization.json) testImplementation(projects.core.datastoreTest) testImplementation(projects.core.testing) } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt index 26f0bbc51..e135d7f58 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt @@ -35,35 +35,35 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface DataModule { +abstract class DataModule { @Binds - fun bindsTopicRepository( + internal abstract fun bindsTopicRepository( topicsRepository: OfflineFirstTopicsRepository, ): TopicsRepository @Binds - fun bindsNewsResourceRepository( + internal abstract fun bindsNewsResourceRepository( newsRepository: OfflineFirstNewsRepository, ): NewsRepository @Binds - fun bindsUserDataRepository( + internal abstract fun bindsUserDataRepository( userDataRepository: OfflineFirstUserDataRepository, ): UserDataRepository @Binds - fun bindsRecentSearchRepository( + internal abstract fun bindsRecentSearchRepository( recentSearchRepository: DefaultRecentSearchRepository, ): RecentSearchRepository @Binds - fun bindsSearchContentsRepository( + internal abstract fun bindsSearchContentsRepository( searchContentsRepository: DefaultSearchContentsRepository, ): SearchContentsRepository @Binds - fun bindsNetworkMonitor( + internal abstract fun bindsNetworkMonitor( networkMonitor: ConnectivityManagerNetworkMonitor, ): NetworkMonitor } diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/UserNewsResourceRepositoryModule.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/UserNewsResourceRepositoryModule.kt index 1a7a80fff..7f4e27b41 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/UserNewsResourceRepositoryModule.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/UserNewsResourceRepositoryModule.kt @@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface UserNewsResourceRepositoryModule { +internal interface UserNewsResourceRepositoryModule { @Binds fun bindsUserNewsResourceRepository( userDataRepository: CompositeUserNewsResourceRepository, diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt index d36f509d9..3d2f657dd 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt @@ -20,7 +20,7 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper -fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBookmarked: Boolean) { +internal fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBookmarked: Boolean) { val eventType = if (isBookmarked) "news_resource_saved" else "news_resource_unsaved" val paramKey = if (isBookmarked) "saved_news_resource_id" else "unsaved_news_resource_id" logEvent( @@ -33,7 +33,7 @@ fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBoo ) } -fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: Boolean) { +internal fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: Boolean) { val eventType = if (isFollowed) "topic_followed" else "topic_unfollowed" val paramKey = if (isFollowed) "followed_topic_id" else "unfollowed_topic_id" logEvent( @@ -46,7 +46,7 @@ fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: B ) } -fun AnalyticsHelper.logThemeChanged(themeName: String) = +internal fun AnalyticsHelper.logThemeChanged(themeName: String) = logEvent( AnalyticsEvent( type = "theme_changed", @@ -56,7 +56,7 @@ fun AnalyticsHelper.logThemeChanged(themeName: String) = ), ) -fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) = +internal fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) = logEvent( AnalyticsEvent( type = "dark_theme_config_changed", @@ -66,7 +66,7 @@ fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) = ), ) -fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) = +internal fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) = logEvent( AnalyticsEvent( type = "dynamic_color_preference_changed", @@ -76,7 +76,7 @@ fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) = ), ) -fun AnalyticsHelper.logOnboardingStateChanged(shouldHideOnboarding: Boolean) { +internal fun AnalyticsHelper.logOnboardingStateChanged(shouldHideOnboarding: Boolean) { val eventType = if (shouldHideOnboarding) "onboarding_complete" else "onboarding_reset" logEvent( AnalyticsEvent(type = eventType), diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt index 702c2dcd2..2e5c4463b 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt @@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.map import kotlinx.datetime.Clock import javax.inject.Inject -class DefaultRecentSearchRepository @Inject constructor( +internal class DefaultRecentSearchRepository @Inject constructor( private val recentSearchQueryDao: RecentSearchQueryDao, ) : RecentSearchRepository { override suspend fun insertOrReplaceRecentSearch(searchQuery: String) { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt index dc3caa143..3bacb8a14 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt @@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.withContext import javax.inject.Inject -class DefaultSearchContentsRepository @Inject constructor( +internal class DefaultSearchContentsRepository @Inject constructor( private val newsResourceDao: NewsResourceDao, private val newsResourceFtsDao: NewsResourceFtsDao, private val topicDao: TopicDao, diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt index ce395ad1c..d33c904e5 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt @@ -45,7 +45,7 @@ private const val SYNC_BATCH_SIZE = 40 * Disk storage backed implementation of the [NewsRepository]. * Reads are exclusively from local storage to support offline access. */ -class OfflineFirstNewsRepository @Inject constructor( +internal class OfflineFirstNewsRepository @Inject constructor( private val niaPreferencesDataSource: NiaPreferencesDataSource, private val newsResourceDao: NewsResourceDao, private val topicDao: TopicDao, diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepository.kt index 13dd19e49..5c8cecce8 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepository.kt @@ -34,7 +34,7 @@ import javax.inject.Inject * Disk storage backed implementation of the [TopicsRepository]. * Reads are exclusively from local storage to support offline access. */ -class OfflineFirstTopicsRepository @Inject constructor( +internal class OfflineFirstTopicsRepository @Inject constructor( private val topicDao: TopicDao, private val network: NiaNetworkDataSource, ) : TopicsRepository { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt index a6e41c9da..c0b1bcc33 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt @@ -25,7 +25,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserData import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class OfflineFirstUserDataRepository @Inject constructor( +internal class OfflineFirstUserDataRepository @Inject constructor( private val niaPreferencesDataSource: NiaPreferencesDataSource, private val analyticsHelper: AnalyticsHelper, ) : UserDataRepository { diff --git a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt index c88125be8..e9599c555 100644 --- a/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt +++ b/core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt @@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.conflate import javax.inject.Inject -class ConnectivityManagerNetworkMonitor @Inject constructor( +internal class ConnectivityManagerNetworkMonitor @Inject constructor( @ApplicationContext private val context: Context, ) : NetworkMonitor { override val isOnline: Flow = callbackFlow { diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index a1075286d..4a6bcb66a 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -30,9 +30,8 @@ android { } dependencies { - implementation(projects.core.model) + api(projects.core.model) - implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.datetime) androidTestImplementation(projects.core.testing) diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt index 34840a733..afbbf42e6 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt @@ -28,7 +28,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -object DaosModule { +internal object DaosModule { @Provides fun providesTopicsDao( database: NiaDatabase, diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseMigrations.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseMigrations.kt index 09e0849fe..4e396c9e2 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseMigrations.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseMigrations.kt @@ -28,7 +28,7 @@ import androidx.room.migration.AutoMigrationSpec * from and Y is the schema version you're migrating to. The class should implement * `AutoMigrationSpec`. */ -object DatabaseMigrations { +internal object DatabaseMigrations { @RenameColumn( tableName = "topics", diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt index 7d89cd1ac..c6e33f284 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt @@ -27,7 +27,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object DatabaseModule { +internal object DatabaseModule { @Provides @Singleton fun providesNiaDatabase( diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/NiaDatabase.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/NiaDatabase.kt index fd6b75e49..87fd82af1 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/NiaDatabase.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/NiaDatabase.kt @@ -63,7 +63,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.InstantConverter @TypeConverters( InstantConverter::class, ) -abstract class NiaDatabase : RoomDatabase() { +internal abstract class NiaDatabase : RoomDatabase() { abstract fun topicDao(): TopicDao abstract fun newsResourceDao(): NewsResourceDao abstract fun topicFtsDao(): TopicFtsDao diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/util/InstantConverter.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/util/InstantConverter.kt index 4e880886c..0b79c2099 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/util/InstantConverter.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/util/InstantConverter.kt @@ -19,7 +19,7 @@ package com.google.samples.apps.nowinandroid.core.database.util import androidx.room.TypeConverter import kotlinx.datetime.Instant -class InstantConverter { +internal class InstantConverter { @TypeConverter fun longToInstant(value: Long?): Instant? = value?.let(Instant::fromEpochMilliseconds) diff --git a/core/datastore-proto/build.gradle.kts b/core/datastore-proto/build.gradle.kts index 157b5336e..511518dde 100644 --- a/core/datastore-proto/build.gradle.kts +++ b/core/datastore-proto/build.gradle.kts @@ -51,5 +51,5 @@ androidComponents.beforeVariants { } dependencies { - implementation(libs.protobuf.kotlin.lite) + api(libs.protobuf.kotlin.lite) } diff --git a/core/datastore-test/build.gradle.kts b/core/datastore-test/build.gradle.kts index 04b15f044..53e5e2c0c 100644 --- a/core/datastore-test/build.gradle.kts +++ b/core/datastore-test/build.gradle.kts @@ -23,10 +23,7 @@ android { } dependencies { - api(projects.core.datastore) - api(libs.androidx.dataStore.core) - - implementation(libs.protobuf.kotlin.lite) + implementation(libs.hilt.android.testing) implementation(projects.core.common) - implementation(projects.core.testing) + implementation(projects.core.datastore) } diff --git a/core/datastore-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt b/core/datastore-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt index b86003e83..295b2978a 100644 --- a/core/datastore-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt +++ b/core/datastore-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/test/TestDataStoreModule.kt @@ -35,7 +35,7 @@ import javax.inject.Singleton components = [SingletonComponent::class], replaces = [DataStoreModule::class], ) -object TestDataStoreModule { +internal object TestDataStoreModule { @Provides @Singleton diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts index afe036640..34ea5ee78 100644 --- a/core/datastore/build.gradle.kts +++ b/core/datastore/build.gradle.kts @@ -33,13 +33,12 @@ android { } dependencies { + api(libs.androidx.dataStore.core) api(projects.core.datastoreProto) + api(projects.core.model) + implementation(projects.core.common) - implementation(projects.core.model) - implementation(libs.androidx.dataStore.core) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.protobuf.kotlin.lite) testImplementation(projects.core.datastoreTest) - testImplementation(projects.core.testing) + testImplementation(libs.kotlinx.coroutines.test) } diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt index 98632c652..ef9c1dd03 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigration.kt @@ -21,7 +21,7 @@ import androidx.datastore.core.DataMigration /** * Migrates saved ids from [Int] to [String] types */ -object IntToStringIdsMigration : DataMigration { +internal object IntToStringIdsMigration : DataMigration { override suspend fun cleanUp() = Unit diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt index 5bff23340..faa491e74 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigration.kt @@ -21,7 +21,7 @@ import androidx.datastore.core.DataMigration /** * Migrates from using lists to maps for user data. */ -object ListToMapMigration : DataMigration { +internal object ListToMapMigration : DataMigration { override suspend fun cleanUp() = Unit diff --git a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt index ac9eaf767..8e0d7d4d8 100644 --- a/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/kotlin/com/google/samples/apps/nowinandroid/core/datastore/di/DataStoreModule.kt @@ -41,7 +41,7 @@ object DataStoreModule { @Provides @Singleton - fun providesUserPreferencesDataStore( + internal fun providesUserPreferencesDataStore( @ApplicationContext context: Context, @Dispatcher(IO) ioDispatcher: CoroutineDispatcher, @ApplicationScope scope: CoroutineScope, diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts index 7bd1d12d8..d68117d06 100644 --- a/core/designsystem/build.gradle.kts +++ b/core/designsystem/build.gradle.kts @@ -17,6 +17,7 @@ plugins { alias(libs.plugins.nowinandroid.android.library) alias(libs.plugins.nowinandroid.android.library.compose) alias(libs.plugins.nowinandroid.android.library.jacoco) + alias(libs.plugins.roborazzi) } android { @@ -39,8 +40,15 @@ dependencies { debugApi(libs.androidx.compose.ui.tooling) - implementation(libs.androidx.core.ktx) implementation(libs.coil.kt.compose) + testImplementation(libs.androidx.compose.ui.test) + testImplementation(libs.accompanist.testharness) + testImplementation(libs.hilt.android.testing) + testImplementation(libs.robolectric) + testImplementation(libs.roborazzi) + testImplementation(projects.core.testing) + + androidTestImplementation(libs.androidx.compose.ui.test) androidTestImplementation(projects.core.testing) } diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts index b81d62102..191877459 100644 --- a/core/domain/build.gradle.kts +++ b/core/domain/build.gradle.kts @@ -24,13 +24,10 @@ android { } dependencies { - implementation(projects.core.data) - implementation(projects.core.model) - implementation(libs.hilt.android) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.kotlinx.datetime) + api(projects.core.data) + api(projects.core.model) - ksp(libs.hilt.compiler) + implementation(libs.javax.inject) testImplementation(projects.core.testing) } \ No newline at end of file diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 393e3aa7d..5d6cabfdf 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -19,5 +19,5 @@ plugins { } dependencies { - implementation(libs.kotlinx.datetime) + api(libs.kotlinx.datetime) } diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index dce97031f..689a99e73 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -39,16 +39,16 @@ secrets { } dependencies { - implementation(projects.core.common) - implementation(projects.core.model) + api(libs.kotlinx.datetime) + api(projects.core.common) + api(projects.core.model) + implementation(libs.coil.kt) implementation(libs.coil.kt.svg) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.okhttp.logging) implementation(libs.retrofit.core) implementation(libs.retrofit.kotlin.serialization) - testImplementation(projects.core.testing) + testImplementation(libs.kotlinx.coroutines.test) } diff --git a/core/network/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt b/core/network/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt index a6162a9cc..850b00201 100644 --- a/core/network/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt +++ b/core/network/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt @@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface FlavoredNetworkModule { +internal interface FlavoredNetworkModule { @Binds fun binds(impl: FakeNiaNetworkDataSource): NiaNetworkDataSource diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt index 98534ba93..21d93c0e4 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/di/NetworkModule.kt @@ -35,7 +35,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object NetworkModule { +internal object NetworkModule { @Provides @Singleton diff --git a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt index b86036ff0..321e856fe 100644 --- a/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt +++ b/core/network/src/main/kotlin/com/google/samples/apps/nowinandroid/core/network/retrofit/RetrofitNiaNetwork.kt @@ -71,7 +71,7 @@ private data class NetworkResponse( * [Retrofit] backed [NiaNetworkDataSource] */ @Singleton -class RetrofitNiaNetwork @Inject constructor( +internal class RetrofitNiaNetwork @Inject constructor( networkJson: Json, okhttpCallFactory: Call.Factory, ) : NiaNetworkDataSource { diff --git a/core/network/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt b/core/network/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt index 51a8a6f19..bff1ca5be 100644 --- a/core/network/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt +++ b/core/network/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/network/di/FlavoredNetworkModule.kt @@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface FlavoredNetworkModule { +internal interface FlavoredNetworkModule { @Binds fun binds(impl: RetrofitNiaNetwork): NiaNetworkDataSource diff --git a/core/notifications/build.gradle.kts b/core/notifications/build.gradle.kts index 31b15a805..92871b72b 100644 --- a/core/notifications/build.gradle.kts +++ b/core/notifications/build.gradle.kts @@ -15,7 +15,6 @@ */ plugins { alias(libs.plugins.nowinandroid.android.library) - alias(libs.plugins.nowinandroid.android.library.compose) alias(libs.plugins.nowinandroid.android.hilt) } @@ -24,14 +23,10 @@ android { } dependencies { - implementation(projects.core.common) - implementation(projects.core.model) + api(projects.core.model) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.androidx.browser) - implementation(libs.androidx.compose.runtime) - implementation(libs.androidx.core.ktx) + implementation(projects.core.common) - implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.cloud.messaging) + compileOnly(platform(libs.androidx.compose.bom)) + compileOnly(libs.androidx.compose.runtime) } diff --git a/core/notifications/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt b/core/notifications/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt index 9bb2b3fb9..99ba10fa7 100644 --- a/core/notifications/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt +++ b/core/notifications/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt @@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -abstract class NotificationsModule { +internal abstract class NotificationsModule { @Binds abstract fun bindNotifier( notifier: NoOpNotifier, diff --git a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NoOpNotifier.kt b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NoOpNotifier.kt index d17005bca..863c1a662 100644 --- a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NoOpNotifier.kt +++ b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NoOpNotifier.kt @@ -22,6 +22,6 @@ import javax.inject.Inject /** * Implementation of [Notifier] which does nothing. Useful for tests and previews. */ -class NoOpNotifier @Inject constructor() : Notifier { +internal class NoOpNotifier @Inject constructor() : Notifier { override fun postNewsNotifications(newsResources: List) = Unit } diff --git a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt index 7e74f819c..73c47879f 100644 --- a/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt +++ b/core/notifications/src/main/kotlin/com/google/samples/apps/nowinandroid/core/notifications/SystemTrayNotifier.kt @@ -50,7 +50,7 @@ private const val FOR_YOU_PATH = "foryou" * Implementation of [Notifier] that displays notifications in the system tray. */ @Singleton -class SystemTrayNotifier @Inject constructor( +internal class SystemTrayNotifier @Inject constructor( @ApplicationContext private val context: Context, ) : Notifier { diff --git a/core/notifications/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt b/core/notifications/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt index 3c05e9c6e..c2e1f76ca 100644 --- a/core/notifications/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt +++ b/core/notifications/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/notifications/NotificationsModule.kt @@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -abstract class NotificationsModule { +internal abstract class NotificationsModule { @Binds abstract fun bindNotifier( notifier: SystemTrayNotifier, diff --git a/core/testing/build.gradle.kts b/core/testing/build.gradle.kts index 8ad91a0d5..275555d80 100644 --- a/core/testing/build.gradle.kts +++ b/core/testing/build.gradle.kts @@ -24,28 +24,23 @@ android { } dependencies { - api(libs.accompanist.testharness) - api(libs.androidx.activity.compose) + api(kotlin("test")) api(libs.androidx.compose.ui.test) - api(libs.androidx.test.core) - api(libs.androidx.test.espresso.core) - api(libs.androidx.test.rules) - api(libs.androidx.test.runner) - api(libs.hilt.android.testing) - api(libs.junit4) - api(libs.kotlinx.coroutines.test) api(libs.roborazzi) - api(libs.robolectric.shadows) - api(libs.turbine) + api(projects.core.analytics) + api(projects.core.data) + api(projects.core.model) + api(projects.core.notifications) 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.data) implementation(projects.core.designsystem) - implementation(projects.core.domain) - implementation(projects.core.model) - implementation(projects.core.notifications) - implementation(projects.core.analytics) - implementation(libs.kotlinx.datetime) } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatcherModule.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatcherModule.kt index 66d52dabe..09c739243 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatcherModule.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatcherModule.kt @@ -26,7 +26,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object TestDispatcherModule { +internal object TestDispatcherModule { @Provides @Singleton fun providesTestDispatcher(): TestDispatcher = UnconfinedTestDispatcher() diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt index f2134105a..4f5d15be1 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/di/TestDispatchersModule.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.test.TestDispatcher components = [SingletonComponent::class], replaces = [DispatchersModule::class], ) -object TestDispatchersModule { +internal object TestDispatchersModule { @Provides @Dispatcher(IO) fun providesIODispatcher(testDispatcher: TestDispatcher): CoroutineDispatcher = testDispatcher diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index c9527d09e..5d8a65d44 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -27,29 +27,14 @@ android { } dependencies { - api(libs.androidx.compose.foundation) - api(libs.androidx.compose.foundation.layout) - api(libs.androidx.compose.material.iconsExtended) - api(libs.androidx.compose.material3) - api(libs.androidx.compose.runtime) - api(libs.androidx.compose.runtime.livedata) - api(libs.androidx.compose.ui.tooling.preview) - api(libs.androidx.compose.ui.util) api(libs.androidx.metrics) - api(libs.androidx.tracing.ktx) + api(projects.core.analytics) + api(projects.core.designsystem) + api(projects.core.model) - debugApi(libs.androidx.compose.ui.tooling) - - implementation(projects.core.analytics) - implementation(projects.core.designsystem) - implementation(projects.core.domain) - implementation(projects.core.model) - implementation(libs.androidx.activity.compose) implementation(libs.androidx.browser) - implementation(libs.androidx.core.ktx) implementation(libs.coil.kt) implementation(libs.coil.kt.compose) - implementation(libs.kotlinx.datetime) androidTestImplementation(projects.core.testing) } diff --git a/feature/bookmarks/build.gradle.kts b/feature/bookmarks/build.gradle.kts index 32394f911..4e97176a2 100644 --- a/feature/bookmarks/build.gradle.kts +++ b/feature/bookmarks/build.gradle.kts @@ -25,5 +25,9 @@ android { } dependencies { - implementation(libs.androidx.compose.material3.windowSizeClass) + implementation(projects.core.data) + + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) } diff --git a/feature/foryou/build.gradle.kts b/feature/foryou/build.gradle.kts index afe769ab8..3f990b902 100644 --- a/feature/foryou/build.gradle.kts +++ b/feature/foryou/build.gradle.kts @@ -25,7 +25,13 @@ android { } dependencies { - implementation(libs.kotlinx.datetime) - implementation(libs.androidx.activity.compose) implementation(libs.accompanist.permissions) + implementation(projects.core.data) + implementation(projects.core.domain) + + testImplementation(libs.hilt.android.testing) + testImplementation(libs.robolectric) + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) } diff --git a/feature/interests/build.gradle.kts b/feature/interests/build.gradle.kts index 20b1ef1aa..f0bca9729 100644 --- a/feature/interests/build.gradle.kts +++ b/feature/interests/build.gradle.kts @@ -22,3 +22,12 @@ plugins { android { namespace = "com.google.samples.apps.nowinandroid.feature.interests" } + +dependencies { + implementation(projects.core.data) + implementation(projects.core.domain) + + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) +} \ No newline at end of file diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts index d96f290e3..206f4c0f9 100644 --- a/feature/search/build.gradle.kts +++ b/feature/search/build.gradle.kts @@ -25,9 +25,14 @@ android { } dependencies { + implementation(projects.core.data) + implementation(projects.core.domain) implementation(projects.feature.bookmarks) implementation(projects.feature.foryou) implementation(projects.feature.interests) - implementation(libs.kotlinx.datetime) + + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) } diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 4f5d649b7..4b9a72bdd 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -26,7 +26,10 @@ android { dependencies { implementation(libs.androidx.appcompat) - implementation(libs.google.oss.licenses) { - exclude(group = "androidx.appcompat") - } + implementation(libs.google.oss.licenses) + implementation(projects.core.data) + + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) } diff --git a/feature/topic/build.gradle.kts b/feature/topic/build.gradle.kts index cc0ecc868..d457b2f73 100644 --- a/feature/topic/build.gradle.kts +++ b/feature/topic/build.gradle.kts @@ -25,5 +25,9 @@ android { } dependencies { - implementation(libs.kotlinx.datetime) + implementation(projects.core.data) + + testImplementation(projects.core.testing) + + androidTestImplementation(projects.core.testing) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa2a32e90..67b267cdc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,26 +9,22 @@ androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" androidxComposeBom = "2023.10.01" androidxComposeCompiler = "1.5.3" -androidxComposeRuntimeTracing = "1.0.0-alpha03" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" androidxDataStore = "1.0.0" androidxEspresso = "3.5.1" androidxHiltNavigationCompose = "1.0.0" -androidxJunit = "1.1.5" androidxLifecycle = "2.6.2" androidxMacroBenchmark = "1.2.0" androidxMetrics = "1.0.0-alpha04" androidxNavigation = "2.7.4" androidxProfileinstaller = "1.3.1" -androidxStartup = "1.1.1" androidxTestCore = "1.5.0" androidxTestExt = "1.1.5" androidxTestRules = "1.5.0" androidxTestRunner = "1.5.2" androidxTracing = "1.1.0" androidxUiAutomator = "2.2.0" -androidxWindowManager = "1.1.0" androidxWork = "2.9.0-rc01" coil = "2.4.0" dependencyGuard = "0.4.3" @@ -41,7 +37,6 @@ googleOssPlugin = "0.10.6" hilt = "2.48.1" hiltExt = "1.1.0" jacoco = "0.8.7" -junit4 = "4.13.2" kotlin = "1.9.10" kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.4.1" @@ -68,15 +63,12 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidxBrowser" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } -androidx-compose-compiler = { group = "androidx.compose.compiler", name = "compiler", version.ref = "androidxComposeCompiler" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } 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-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } -androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" } -androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidxComposeRuntimeTracing" } 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" } @@ -86,7 +78,6 @@ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" } androidx-dataStore-core = { group = "androidx.datastore", name = "datastore", version.ref = "androidxDataStore" } androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" } -androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "androidxLifecycle" } androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" } androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" } androidx-metrics = { group = "androidx.metrics", name = "metrics-performance", version.ref = "androidxMetrics" } @@ -100,7 +91,6 @@ androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = " androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" } androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" } androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" } -androidx-window-manager = { module = "androidx.window:window", version.ref = "androidxWindowManager" } androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "androidxWork" } androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "androidxWork" } coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" } @@ -111,7 +101,6 @@ firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.r firebase-cloud-messaging = { group = "com.google.firebase", name = "firebase-messaging-ktx" } firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } firebase-performance = { group = "com.google.firebase", name = "firebase-perf-ktx" } -firebase-performance-gradle = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin" } 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" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } @@ -119,9 +108,8 @@ hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testi hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltExt" } hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" } -junit4 = { group = "junit", name = "junit", version.ref = "junit4" } +javax-inject = { module = "javax.inject:javax.inject", version = "1" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } -kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } @@ -145,7 +133,6 @@ turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine # Dependencies of the included build-logic android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsPlugin" } firebase-performance-gradlePlugin = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/sync/sync-test/build.gradle.kts b/sync/sync-test/build.gradle.kts index 02e573ae5..0fac2cf83 100644 --- a/sync/sync-test/build.gradle.kts +++ b/sync/sync-test/build.gradle.kts @@ -23,7 +23,7 @@ android { } dependencies { - api(projects.sync.work) + implementation(libs.hilt.android.testing) implementation(projects.core.data) - implementation(projects.core.testing) + implementation(projects.sync.work) } diff --git a/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/NeverSyncingSyncManager.kt b/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/NeverSyncingSyncManager.kt index 2b0b4fb6a..c13b409e6 100644 --- a/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/NeverSyncingSyncManager.kt +++ b/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/NeverSyncingSyncManager.kt @@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import javax.inject.Inject -class NeverSyncingSyncManager @Inject constructor() : SyncManager { +internal class NeverSyncingSyncManager @Inject constructor() : SyncManager { override val isSyncing: Flow = flowOf(false) override fun requestSync() = Unit } diff --git a/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/TestSyncModule.kt b/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/TestSyncModule.kt index 0089450b5..d1c0f562f 100644 --- a/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/TestSyncModule.kt +++ b/sync/sync-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/sync/test/TestSyncModule.kt @@ -28,7 +28,7 @@ import dagger.hilt.testing.TestInstallIn components = [SingletonComponent::class], replaces = [SyncModule::class], ) -interface TestSyncModule { +internal interface TestSyncModule { @Binds fun bindsSyncStatusMonitor( syncStatusMonitor: NeverSyncingSyncManager, diff --git a/sync/work/build.gradle.kts b/sync/work/build.gradle.kts index 867aef17c..7e61c7389 100644 --- a/sync/work/build.gradle.kts +++ b/sync/work/build.gradle.kts @@ -27,23 +27,19 @@ android { } dependencies { - implementation(projects.core.analytics) - implementation(projects.core.common) - implementation(projects.core.data) - implementation(projects.core.datastore) - implementation(projects.core.model) - implementation(libs.androidx.lifecycle.livedata.ktx) + ksp(libs.hilt.ext.compiler) + implementation(libs.androidx.tracing.ktx) implementation(libs.androidx.work.ktx) implementation(libs.hilt.ext.work) - implementation(libs.kotlinx.coroutines.android) + implementation(projects.core.analytics) + implementation(projects.core.data) prodImplementation(libs.firebase.cloud.messaging) + prodImplementation(platform(libs.firebase.bom)) - ksp(libs.hilt.ext.compiler) - - testImplementation(projects.core.testing) - - androidTestImplementation(projects.core.testing) androidTestImplementation(libs.androidx.work.testing) + androidTestImplementation(libs.hilt.android.testing) + androidTestImplementation(libs.kotlinx.coroutines.guava) + androidTestImplementation(projects.core.testing) } diff --git a/sync/work/src/demo/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt b/sync/work/src/demo/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt index 40d094cd2..91ef476f6 100644 --- a/sync/work/src/demo/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt +++ b/sync/work/src/demo/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt @@ -27,14 +27,14 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -interface SyncModule { +abstract class SyncModule { @Binds - fun bindsSyncStatusMonitor( + internal abstract fun bindsSyncStatusMonitor( syncStatusMonitor: WorkManagerSyncManager, ): SyncManager @Binds - fun bindsSyncSubscriber( + internal abstract fun bindsSyncSubscriber( syncSubscriber: StubSyncSubscriber, ): SyncSubscriber } diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/StubSyncSubscriber.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/StubSyncSubscriber.kt index 0ef90fb29..83286eeec 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/StubSyncSubscriber.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/StubSyncSubscriber.kt @@ -24,7 +24,7 @@ private const val TAG = "StubSyncSubscriber" /** * Stub implementation of [SyncSubscriber] */ -class StubSyncSubscriber @Inject constructor() : SyncSubscriber { +internal class StubSyncSubscriber @Inject constructor() : SyncSubscriber { override suspend fun subscribe() { Log.d(TAG, "Subscribing to sync") } diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt index 9131e4888..77a993736 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt @@ -33,7 +33,7 @@ import javax.inject.Inject /** * [SyncManager] backed by [WorkInfo] from [WorkManager] */ -class WorkManagerSyncManager @Inject constructor( +internal class WorkManagerSyncManager @Inject constructor( @ApplicationContext private val context: Context, ) : SyncManager { override val isSyncing: Flow = diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt index d5250b330..f2d9283c0 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/AnalyticsExtensions.kt @@ -19,12 +19,12 @@ package com.google.samples.apps.nowinandroid.sync.workers import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper -fun AnalyticsHelper.logSyncStarted() = +internal fun AnalyticsHelper.logSyncStarted() = logEvent( AnalyticsEvent(type = "network_sync_started"), ) -fun AnalyticsHelper.logSyncFinished(syncedSuccessfully: Boolean) { +internal fun AnalyticsHelper.logSyncFinished(syncedSuccessfully: Boolean) { val eventType = if (syncedSuccessfully) "network_sync_successful" else "network_sync_failed" logEvent( AnalyticsEvent(type = eventType), diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt index 1948b49a3..ea5f36042 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/workers/SyncWorker.kt @@ -48,7 +48,7 @@ import kotlinx.coroutines.withContext * sync functionality. */ @HiltWorker -class SyncWorker @AssistedInject constructor( +internal class SyncWorker @AssistedInject constructor( @Assisted private val appContext: Context, @Assisted workerParams: WorkerParameters, private val niaPreferences: NiaPreferencesDataSource, diff --git a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt index af4508406..df6db419b 100644 --- a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt +++ b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/di/SyncModule.kt @@ -32,20 +32,20 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -interface SyncModule { +abstract class SyncModule { @Binds - fun bindsSyncStatusMonitor( + internal abstract fun bindsSyncStatusMonitor( syncStatusMonitor: WorkManagerSyncManager, ): SyncManager @Binds - fun bindsSyncSubscriber( + internal abstract fun bindsSyncSubscriber( syncSubscriber: FirebaseSyncSubscriber, ): SyncSubscriber companion object { @Provides @Singleton - fun provideFirebaseMessaging(): FirebaseMessaging = Firebase.messaging + internal fun provideFirebaseMessaging(): FirebaseMessaging = Firebase.messaging } } diff --git a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/services/SyncNotificationsService.kt b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/services/SyncNotificationsService.kt index e51e30164..c7297dd1a 100644 --- a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/services/SyncNotificationsService.kt +++ b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/services/SyncNotificationsService.kt @@ -25,7 +25,7 @@ import javax.inject.Inject private const val SYNC_TOPIC_SENDER = "/topics/sync" @AndroidEntryPoint -class SyncNotificationsService : FirebaseMessagingService() { +internal class SyncNotificationsService : FirebaseMessagingService() { @Inject lateinit var syncManager: SyncManager diff --git a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/status/FirebaseSyncSubscriber.kt b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/status/FirebaseSyncSubscriber.kt index c2405bccc..2c48488e6 100644 --- a/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/status/FirebaseSyncSubscriber.kt +++ b/sync/work/src/prod/kotlin/com/google/samples/apps/nowinandroid/sync/status/FirebaseSyncSubscriber.kt @@ -24,7 +24,7 @@ import javax.inject.Inject /** * Implementation of [SyncSubscriber] that subscribes to the FCM [SYNC_TOPIC] */ -class FirebaseSyncSubscriber @Inject constructor( +internal class FirebaseSyncSubscriber @Inject constructor( private val firebaseMessaging: FirebaseMessaging, ) : SyncSubscriber { override suspend fun subscribe() { From 65a8b0d3b549dbed9719561d239f3fccd33ac45a Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 20:45:33 +0100 Subject: [PATCH 27/71] dependencyGuardBaseline --- .../dependencies/releaseRuntimeClasspath.txt | 87 +++---------------- .../prodReleaseRuntimeClasspath.txt | 13 +-- 2 files changed, 14 insertions(+), 86 deletions(-) diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 786b15d57..7db29f30c 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -26,7 +26,6 @@ androidx.compose.material:material-icons-extended:1.5.4 androidx.compose.material:material-ripple-android:1.5.4 androidx.compose.material:material-ripple:1.5.4 androidx.compose.runtime:runtime-android:1.5.4 -androidx.compose.runtime:runtime-livedata:1.5.4 androidx.compose.runtime:runtime-saveable-android:1.5.4 androidx.compose.runtime:runtime-saveable:1.5.4 androidx.compose.runtime:runtime:1.5.4 @@ -46,18 +45,14 @@ androidx.compose.ui:ui-util:1.5.4 androidx.compose.ui:ui:1.5.4 androidx.compose:compose-bom:2023.10.01 androidx.concurrent:concurrent-futures:1.1.0 -androidx.core:core-ktx:1.12.0 -androidx.core:core:1.12.0 +androidx.core:core-ktx:1.10.0 +androidx.core:core:1.10.0 androidx.customview:customview-poolingcontainer:1.0.0 androidx.customview:customview:1.0.0 -androidx.datastore:datastore-core:1.0.0 -androidx.datastore:datastore:1.0.0 -androidx.documentfile:documentfile:1.0.0 androidx.emoji2:emoji2:1.4.0 androidx.exifinterface:exifinterface:1.3.6 androidx.fragment:fragment:1.5.1 androidx.interpolator:interpolator:1.0.0 -androidx.legacy:legacy-support-core-utils:1.0.0 androidx.lifecycle:lifecycle-common-java8:2.6.1 androidx.lifecycle:lifecycle-common:2.6.1 androidx.lifecycle:lifecycle-livedata-core:2.6.1 @@ -69,99 +64,39 @@ androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1 androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1 androidx.lifecycle:lifecycle-viewmodel:2.6.1 androidx.loader:loader:1.0.0 -androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 androidx.metrics:metrics-performance:1.0.0-alpha04 -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.room:room-common:2.6.0 -androidx.room:room-ktx:2.6.0 -androidx.room:room-runtime:2.6.0 androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate:1.2.1 -androidx.sqlite:sqlite-framework:2.4.0 -androidx.sqlite:sqlite:2.4.0 androidx.startup:startup-runtime:1.1.1 -androidx.tracing:tracing-ktx:1.1.0 -androidx.tracing:tracing:1.1.0 +androidx.tracing:tracing:1.0.0 androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -com.caverock:androidsvg-aar:1.4 com.google.accompanist:accompanist-drawablepainter:0.30.1 -com.google.android.datatransport:transport-api:3.0.0 -com.google.android.datatransport:transport-backend-cct:3.1.8 -com.google.android.datatransport:transport-runtime:3.1.8 -com.google.android.gms:play-services-ads-identifier:18.0.0 -com.google.android.gms:play-services-base:18.0.1 -com.google.android.gms:play-services-basement:18.1.0 -com.google.android.gms:play-services-cloud-messaging:17.0.1 -com.google.android.gms:play-services-measurement-api:21.4.0 -com.google.android.gms:play-services-measurement-base:21.4.0 -com.google.android.gms:play-services-measurement-impl:21.4.0 -com.google.android.gms:play-services-measurement-sdk-api:21.4.0 -com.google.android.gms:play-services-measurement-sdk:21.4.0 -com.google.android.gms:play-services-measurement:21.4.0 -com.google.android.gms:play-services-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.48.1 com.google.dagger:dagger:2.48.1 com.google.dagger:hilt-android:2.48.1 com.google.dagger:hilt-core:2.48.1 -com.google.errorprone:error_prone_annotations:2.11.0 -com.google.firebase:firebase-analytics-ktx:21.4.0 -com.google.firebase:firebase-analytics:21.4.0 -com.google.firebase:firebase-annotations:16.2.0 -com.google.firebase:firebase-bom:32.4.0 -com.google.firebase:firebase-common-ktx:20.4.2 -com.google.firebase:firebase-common:20.4.2 -com.google.firebase:firebase-components:17.1.5 -com.google.firebase:firebase-datatransport:18.1.7 -com.google.firebase:firebase-encoders-json:18.0.0 -com.google.firebase:firebase-encoders-proto:16.0.0 -com.google.firebase:firebase-encoders:17.0.0 -com.google.firebase:firebase-iid-interop:17.1.0 -com.google.firebase:firebase-installations-interop:17.1.1 -com.google.firebase:firebase-installations:17.2.0 -com.google.firebase:firebase-measurement-connector:19.0.0 -com.google.firebase:firebase-messaging-ktx:23.3.0 -com.google.firebase:firebase-messaging:23.3.0 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:31.1-android -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.j2objc:j2objc-annotations:1.3 -com.google.protobuf:protobuf-javalite:3.24.4 -com.google.protobuf:protobuf-kotlin-lite:3.24.4 -com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0 -com.squareup.okhttp3:logging-interceptor:4.12.0 -com.squareup.okhttp3:okhttp:4.12.0 -com.squareup.okio:okio-jvm:3.6.0 -com.squareup.okio:okio:3.6.0 -com.squareup.retrofit2:retrofit:2.9.0 +com.google.guava:listenablefuture:1.0 +com.squareup.okhttp3:okhttp:4.11.0 +com.squareup.okio:okio-jvm:3.3.0 +com.squareup.okio:okio:3.3.0 io.coil-kt:coil-base:2.4.0 io.coil-kt:coil-compose-base:2.4.0 io.coil-kt:coil-compose:2.4.0 -io.coil-kt:coil-svg:2.4.0 io.coil-kt:coil:2.4.0 javax.inject:javax.inject:1 -org.checkerframework:checker-qual:3.12.0 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10 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.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.1 org.jetbrains.kotlinx:kotlinx-datetime:0.4.1 -org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0 -org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.0 -org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0 -org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0 -org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0 org.jetbrains:annotations:23.0.0 diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 8123217b9..878a5c462 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -29,10 +29,8 @@ androidx.compose.material:material-icons-extended:1.5.4 androidx.compose.material:material-ripple-android:1.5.4 androidx.compose.material:material-ripple:1.5.4 androidx.compose.runtime:runtime-android:1.5.4 -androidx.compose.runtime:runtime-livedata:1.5.4 androidx.compose.runtime:runtime-saveable-android:1.5.4 androidx.compose.runtime:runtime-saveable:1.5.4 -androidx.compose.runtime:runtime-tracing:1.0.0-alpha03 androidx.compose.runtime:runtime:1.5.4 androidx.compose.ui:ui-android:1.5.4 androidx.compose.ui:ui-geometry-android:1.5.4 @@ -74,9 +72,7 @@ androidx.interpolator:interpolator:1.0.0 androidx.legacy:legacy-support-core-utils:1.0.0 androidx.lifecycle:lifecycle-common-java8:2.6.2 androidx.lifecycle:lifecycle-common:2.6.2 -androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.2 androidx.lifecycle:lifecycle-livedata-core:2.6.2 -androidx.lifecycle:lifecycle-livedata-ktx:2.6.2 androidx.lifecycle:lifecycle-livedata:2.6.2 androidx.lifecycle:lifecycle-process:2.6.2 androidx.lifecycle:lifecycle-runtime-compose:2.6.2 @@ -108,16 +104,13 @@ androidx.savedstate:savedstate:1.2.1 androidx.sqlite:sqlite-framework:2.4.0 androidx.sqlite:sqlite:2.4.0 androidx.startup:startup-runtime:1.1.1 -androidx.tracing:tracing-ktx:1.2.0-alpha02 -androidx.tracing:tracing-perfetto-common:1.0.0-alpha11 -androidx.tracing:tracing-perfetto:1.0.0-alpha11 -androidx.tracing:tracing:1.2.0-alpha02 +androidx.tracing:tracing-ktx:1.1.0 +androidx.tracing:tracing:1.1.0 androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0 androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.viewpager:viewpager:1.0.0 -androidx.window.extensions.core:core:1.0.0 -androidx.window:window:1.1.0 +androidx.window:window:1.0.0 androidx.work:work-runtime-ktx:2.9.0-rc01 androidx.work:work-runtime:2.9.0-rc01 com.caverock:androidsvg-aar:1.4 From 2b739c0ffe5e49ab719b71f8d7a4b9624785621c Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 9 Dec 2023 21:23:13 +0100 Subject: [PATCH 28/71] Fix badging, which is not "stable" when it comes to ordering --- app/prodRelease-badging.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/prodRelease-badging.txt b/app/prodRelease-badging.txt index 6c3a859c7..9ae76fff2 100644 --- a/app/prodRelease-badging.txt +++ b/app/prodRelease-badging.txt @@ -105,9 +105,9 @@ application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml' application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml' application: label='Now in Android' icon='res/mipmap-anydpi-v26/ic_launcher.xml' launchable-activity: name='com.google.samples.apps.nowinandroid.MainActivity' label='' icon='' +uses-library-not-required:'android.ext.adservices' uses-library-not-required:'androidx.window.extensions' uses-library-not-required:'androidx.window.sidecar' -uses-library-not-required:'android.ext.adservices' feature-group: label='' uses-feature: name='android.hardware.faketouch' uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' From 0490e014f6b1e99811d26dacff92268c6704f29d Mon Sep 17 00:00:00 2001 From: lihenggui Date: Thu, 14 Dec 2023 14:26:34 -0800 Subject: [PATCH 30/71] Update Spotless and ktlint --- .editorconfig | 1 + gradle/init.gradle.kts | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 83be1d5a0..2d5a46b3c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,3 +4,4 @@ [*.{kt,kts}] ij_kotlin_allow_trailing_comma=true ij_kotlin_allow_trailing_comma_on_call_site=true +ktlint_function_naming_ignore_when_annotated_with=Composable diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index 72f3eebe0..fe79fa01e 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -14,10 +14,10 @@ * limitations under the License. */ -val ktlintVersion = "0.48.1" +val ktlintVersion = "1.0.1" initscript { - val spotlessVersion = "6.22.0" + val spotlessVersion = "6.23.3" repositories { mavenCentral() @@ -35,7 +35,11 @@ rootProject { kotlin { target("**/*.kt") targetExclude("**/build/**/*.kt") - ktlint(ktlintVersion).userData(mapOf("android" to "true")) + ktlint(ktlintVersion).editorConfigOverride( + mapOf( + "android" to "true", + ), + ) licenseHeaderFile(rootProject.file("spotless/copyright.kt")) } format("kts") { From ff599eac37ad179fbf2bdcc1783f4c3493c972af Mon Sep 17 00:00:00 2001 From: lihenggui Date: Thu, 14 Dec 2023 14:46:37 -0800 Subject: [PATCH 31/71] Ignore property-naming and discouraged-comment-location --- gradle/init.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index fe79fa01e..a06d659fa 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -38,6 +38,8 @@ rootProject { ktlint(ktlintVersion).editorConfigOverride( mapOf( "android" to "true", + "ktlint_standard_property-naming" to "disabled", + "ktlint_standard_discouraged-comment-location" to "disabled", ), ) licenseHeaderFile(rootProject.file("spotless/copyright.kt")) From fb5bd225be83aff2c444aca999c92986c793a11f Mon Sep 17 00:00:00 2001 From: lihenggui Date: Thu, 14 Dec 2023 14:49:25 -0800 Subject: [PATCH 32/71] Spotless --- .../samples/apps/nowinandroid/MainActivity.kt | 2 +- .../core/analytics/AnalyticsModule.kt | 4 +- .../nowinandroid/core/result/ResultKtTest.kt | 2 +- .../datastore/IntToStringIdsMigrationTest.kt | 4 +- .../core/datastore/ListToMapMigrationTest.kt | 6 +- .../core/designsystem/component/Button.kt | 2 +- .../component/scrollbar/AppScrollbars.kt | 4 +- .../LoadingWheelScreenshotTests.kt | 2 +- .../core/designsystem/TagScreenshotTests.kt | 2 +- .../core/model/data/DarkThemeConfig.kt | 4 +- .../core/model/data/ThemeBrand.kt | 3 +- .../fake/FakeNiaNetworkDataSourceTest.kt | 80 +++++++++---------- .../testing/data/FollowableTopicTestData.kt | 3 +- .../testing/data/NewsResourcesTestData.kt | 3 +- .../core/testing/data/TopicsTestData.kt | 3 +- .../testing/data/UserNewsResourcesTestData.kt | 3 +- ...FollowableTopicPreviewParameterProvider.kt | 3 +- ...serNewsResourcePreviewParameterProvider.kt | 3 +- .../SearchUiStatePreviewParameterProvider.kt | 3 +- .../feature/settings/SettingsDialog.kt | 3 +- 20 files changed, 77 insertions(+), 62 deletions(-) diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt index 7fe1bc674..1f2bc49b9 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt @@ -179,7 +179,7 @@ class MainActivity : ComponentActivity() { To see quick turnaround of the ProfileVerifier, we recommend using `speed-profile`. If you don't do either of these steps, you might only see the profile status reported as "enqueued for compilation" when running the sample locally. - */ + */ withContext(Dispatchers.IO) { val status = ProfileVerifier.getCompilationStatusAsync().await() Log.d(TAG, "ProfileInstaller status code: ${status.profileInstallResultCode}") diff --git a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt index 9f875ae6d..0d2e0e274 100644 --- a/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt +++ b/core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt @@ -35,6 +35,8 @@ abstract class AnalyticsModule { companion object { @Provides @Singleton - fun provideFirebaseAnalytics(): FirebaseAnalytics { return Firebase.analytics } + fun provideFirebaseAnalytics(): FirebaseAnalytics { + return Firebase.analytics + } } } diff --git a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt index 4f1229e9d..512a1a2f2 100644 --- a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt +++ b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt @@ -25,7 +25,7 @@ import kotlin.test.assertEquals class ResultKtTest { @Test - fun Result_catches_errors() = runTest { + fun result_catches_errors() = runTest { flow { emit(1) throw Exception("Test Done") diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt index 8b97cff34..392ca2550 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt @@ -27,7 +27,7 @@ import kotlin.test.assertTrue class IntToStringIdsMigrationTest { @Test - fun IntToStringIdsMigration_should_migrate_topic_ids() = runTest { + fun intToStringIdsMigration_should_migrate_topic_ids() = runTest { // Set up existing preferences with topic int ids val preMigrationUserPreferences = userPreferences { deprecatedIntFollowedTopicIds.addAll(listOf(1, 2, 3)) @@ -56,7 +56,7 @@ class IntToStringIdsMigrationTest { } @Test - fun IntToStringIdsMigration_should_migrate_author_ids() = runTest { + fun intToStringIdsMigration_should_migrate_author_ids() = runTest { // Set up existing preferences with author int ids val preMigrationUserPreferences = userPreferences { deprecatedIntFollowedAuthorIds.addAll(listOf(4, 5, 6)) diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt index f7e083b45..41404d6a9 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt @@ -24,7 +24,7 @@ import kotlin.test.assertTrue class ListToMapMigrationTest { @Test - fun ListToMapMigration_should_migrate_topic_ids() = runTest { + fun listToMapMigration_should_migrate_topic_ids() = runTest { // Set up existing preferences with topic ids val preMigrationUserPreferences = userPreferences { deprecatedFollowedTopicIds.addAll(listOf("1", "2", "3")) @@ -50,7 +50,7 @@ class ListToMapMigrationTest { } @Test - fun ListToMapMigration_should_migrate_author_ids() = runTest { + fun listToMapMigration_should_migrate_author_ids() = runTest { // Set up existing preferences with author ids val preMigrationUserPreferences = userPreferences { deprecatedFollowedAuthorIds.addAll(listOf("4", "5", "6")) @@ -76,7 +76,7 @@ class ListToMapMigrationTest { } @Test - fun ListToMapMigration_should_migrate_bookmarks() = runTest { + fun listToMapMigration_should_migrate_bookmarks() = runTest { // Set up existing preferences with bookmarks val preMigrationUserPreferences = userPreferences { deprecatedBookmarkedNewsResourceIds.addAll(listOf("7", "8", "9")) diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt index 966014fd9..a5ecc86c1 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt @@ -278,7 +278,7 @@ fun NiaButtonPreview() { @ThemePreviews @Composable fun NiaOutlinedButtonPreview() { - NiaTheme() { + NiaTheme { NiaBackground(modifier = Modifier.size(150.dp, 50.dp)) { NiaOutlinedButton(onClick = {}, text = { Text("Test button") }) } 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 fe14bce14..c8102073a 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 @@ -251,5 +251,7 @@ private fun scrollbarThumbColor( } private enum class ThumbState { - Active, Inactive, Dormant + Active, + Inactive, + Dormant, } diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/LoadingWheelScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/LoadingWheelScreenshotTests.kt index 75a4aec3a..9f80d04e5 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/LoadingWheelScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/LoadingWheelScreenshotTests.kt @@ -66,7 +66,7 @@ class LoadingWheelScreenshotTests() { fun loadingWheelAnimation() { composeTestRule.mainClock.autoAdvance = false composeTestRule.setContent { - NiaTheme() { + NiaTheme { NiaLoadingWheel(contentDesc = "") } } diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt index d9edfd6c6..16ed71aeb 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt @@ -47,7 +47,7 @@ class TagScreenshotTests() { val composeTestRule = createAndroidComposeRule() @Test - fun Tag_multipleThemes() { + fun tag_multipleThemes() { composeTestRule.captureMultiTheme("Tag") { NiaTopicTag(followed = true, onClick = {}) { Text("TOPIC") diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/DarkThemeConfig.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/DarkThemeConfig.kt index f130a70db..dcbcaa531 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/DarkThemeConfig.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/DarkThemeConfig.kt @@ -17,5 +17,7 @@ package com.google.samples.apps.nowinandroid.core.model.data enum class DarkThemeConfig { - FOLLOW_SYSTEM, LIGHT, DARK + FOLLOW_SYSTEM, + LIGHT, + DARK, } diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/ThemeBrand.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/ThemeBrand.kt index d8953df3c..431d4b573 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/ThemeBrand.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/ThemeBrand.kt @@ -17,5 +17,6 @@ package com.google.samples.apps.nowinandroid.core.model.data enum class ThemeBrand { - DEFAULT, ANDROID + DEFAULT, + ANDROID, } diff --git a/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt b/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt index 76c2accf2..147252cf1 100644 --- a/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt +++ b/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt @@ -45,46 +45,46 @@ class FakeNiaNetworkDataSourceTest { } @Test - fun testDeserializationOfTopics() = runTest(testDispatcher) { - assertEquals( - /* ktlint-disable max-line-length */ - NetworkTopic( - id = "1", - name = "Headlines", - shortDescription = "News you'll definitely be interested in", - longDescription = "The latest events and announcements from the world of Android development.", - url = "", - imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Headlines.svg?alt=media&token=506faab0-617a-4668-9e63-4a2fb996603f", - ), - /* ktlint-enable max-line-length */ - subject.getTopics().first(), - ) - } + fun testDeserializationOfTopics() = + @Suppress("ktlint:standard:max-line-length") + runTest(testDispatcher) { + assertEquals( + NetworkTopic( + id = "1", + name = "Headlines", + shortDescription = "News you'll definitely be interested in", + longDescription = "The latest events and announcements from the world of Android development.", + url = "", + imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Headlines.svg?alt=media&token=506faab0-617a-4668-9e63-4a2fb996603f", + ), + subject.getTopics().first(), + ) + } @Test - fun testDeserializationOfNewsResources() = runTest(testDispatcher) { - assertEquals( - /* ktlint-disable max-line-length */ - NetworkNewsResource( - id = "125", - title = "Android Basics with Compose", - content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. ", - url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html", - headerImageUrl = "https://developer.android.com/images/hero-assets/android-basics-compose.svg", - publishDate = LocalDateTime( - year = 2022, - monthNumber = 5, - dayOfMonth = 4, - hour = 23, - minute = 0, - second = 0, - nanosecond = 0, - ).toInstant(TimeZone.UTC), - type = "Codelab", - topics = listOf("2", "3", "10"), - ), - /* ktlint-enable max-line-length */ - subject.getNewsResources().find { it.id == "125" }, - ) - } + fun testDeserializationOfNewsResources() = + @Suppress("ktlint:standard:max-line-length") + runTest(testDispatcher) { + assertEquals( + NetworkNewsResource( + id = "125", + title = "Android Basics with Compose", + content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. ", + url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html", + headerImageUrl = "https://developer.android.com/images/hero-assets/android-basics-compose.svg", + publishDate = LocalDateTime( + year = 2022, + monthNumber = 5, + dayOfMonth = 4, + hour = 23, + minute = 0, + second = 0, + nanosecond = 0, + ).toInstant(TimeZone.UTC), + type = "Codelab", + topics = listOf("2", "3", "10"), + ), + subject.getNewsResources().find { it.id == "125" }, + ) + } } diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/FollowableTopicTestData.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/FollowableTopicTestData.kt index a96326a62..9b85516e7 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/FollowableTopicTestData.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/FollowableTopicTestData.kt @@ -14,12 +14,13 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.testing.data import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.Topic -/* ktlint-disable max-line-length */ val followableTopicTestData: List = listOf( FollowableTopic( topic = Topic( diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/NewsResourcesTestData.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/NewsResourcesTestData.kt index ef845dc58..b3fff7ca0 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/NewsResourcesTestData.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/NewsResourcesTestData.kt @@ -14,12 +14,13 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.testing.data import com.google.samples.apps.nowinandroid.core.model.data.NewsResource import kotlinx.datetime.Instant -/* ktlint-disable max-line-length */ val newsResourcesTestData: List = listOf( NewsResource( id = "1", diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/TopicsTestData.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/TopicsTestData.kt index 90e9166a5..fc6ef62a6 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/TopicsTestData.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/TopicsTestData.kt @@ -14,11 +14,12 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.testing.data import com.google.samples.apps.nowinandroid.core.model.data.Topic -/* ktlint-disable max-line-length */ val topicsTestData: List = listOf( Topic( id = "2", diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt index 97acad088..4174391df 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/data/UserNewsResourcesTestData.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.testing.data import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig @@ -26,7 +28,6 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant -/* ktlint-disable max-line-length */ val userNewsResourcesTestData: List = UserData( bookmarkedNewsResources = setOf("1", "4"), viewedNewsResources = setOf("1", "2", "4"), diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/FollowableTopicPreviewParameterProvider.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/FollowableTopicPreviewParameterProvider.kt index 2132f5b16..16ae1eced 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/FollowableTopicPreviewParameterProvider.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/FollowableTopicPreviewParameterProvider.kt @@ -14,13 +14,14 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.ui import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.model.data.Topic -/* ktlint-disable max-line-length */ /** * This [PreviewParameterProvider](https://developer.android.com/reference/kotlin/androidx/compose/ui/tooling/preview/PreviewParameterProvider) * provides list of [FollowableTopic] for Composable previews. diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt index 493788314..3189e8403 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/UserNewsResourcePreviewParameterProvider.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.core.ui import androidx.compose.ui.tooling.preview.PreviewParameterProvider @@ -29,7 +31,6 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant -/* ktlint-disable max-line-length */ /** * This [PreviewParameterProvider](https://developer.android.com/reference/kotlin/androidx/compose/ui/tooling/preview/PreviewParameterProvider) * provides list of [UserNewsResource] for Composable previews. diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchUiStatePreviewParameterProvider.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchUiStatePreviewParameterProvider.kt index 4268893da..257d8b68e 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchUiStatePreviewParameterProvider.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchUiStatePreviewParameterProvider.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.feature.search import androidx.compose.ui.tooling.preview.PreviewParameterProvider @@ -21,7 +23,6 @@ import com.google.samples.apps.nowinandroid.core.model.data.FollowableTopic import com.google.samples.apps.nowinandroid.core.ui.PreviewParameterData.newsResources import com.google.samples.apps.nowinandroid.core.ui.PreviewParameterData.topics -/* ktlint-disable max-line-length */ /** * This [PreviewParameterProvider](https://developer.android.com/reference/kotlin/androidx/compose/ui/tooling/preview/PreviewParameterProvider) * provides list of [SearchResultUiState] for Composable previews. diff --git a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index 01ec30e74..b6ce77d1a 100644 --- a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("ktlint:standard:max-line-length") + package com.google.samples.apps.nowinandroid.feature.settings import android.content.Intent @@ -316,7 +318,6 @@ private fun PreviewSettingsDialogLoading() { } } -/* ktlint-disable max-line-length */ private const val PRIVACY_POLICY_URL = "https://policies.google.com/privacy" private const val BRAND_GUIDELINES_URL = "https://developer.android.com/distribute/marketing-tools/brand-guidelines" private const val FEEDBACK_URL = "https://goo.gle/nia-app-feedback" From f8f932705a71d47f4925f5cad9bbbcacdb3cc0c1 Mon Sep 17 00:00:00 2001 From: lihenggui Date: Thu, 14 Dec 2023 14:56:11 -0800 Subject: [PATCH 33/71] Remove violations: discouraged-comment-location --- .../samples/apps/nowinandroid/startup/StartupBenchmark.kt | 3 ++- .../apps/nowinandroid/core/model/data/FollowableTopic.kt | 3 ++- .../apps/nowinandroid/core/testing/util/ScreenshotHelper.kt | 6 ++++-- .../samples/apps/nowinandroid/core/ui/NewsResourceCard.kt | 6 ++++-- .../apps/nowinandroid/feature/foryou/ForYouScreen.kt | 3 ++- .../apps/nowinandroid/feature/interests/InterestsItem.kt | 3 ++- .../apps/nowinandroid/feature/search/SearchScreen.kt | 3 ++- gradle/init.gradle.kts | 1 - 8 files changed, 18 insertions(+), 10 deletions(-) diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt index ace7f14e4..96bea89b8 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt @@ -60,7 +60,8 @@ class StartupBenchmark { packageName = PACKAGE_NAME, metrics = listOf(StartupTimingMetric()), compilationMode = compilationMode, - iterations = 20, // More iterations result in higher statistical significance. + // More iterations result in higher statistical significance. + iterations = 20, startupMode = COLD, setupBlock = { pressHome() diff --git a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/FollowableTopic.kt b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/FollowableTopic.kt index cef319c5f..ae1d525c5 100644 --- a/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/FollowableTopic.kt +++ b/core/model/src/main/kotlin/com/google/samples/apps/nowinandroid/core/model/data/FollowableTopic.kt @@ -19,7 +19,8 @@ package com.google.samples.apps.nowinandroid.core.model.data /** * A [topic] with the additional information for whether or not it is followed. */ -data class FollowableTopic( // TODO consider changing to UserTopic and flattening +// TODO consider changing to UserTopic and flattening +data class FollowableTopic( val topic: Topic, val isFollowed: Boolean, ) diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index e84fe7d33..0f00ff16d 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -38,8 +38,10 @@ import org.robolectric.RuntimeEnvironment val DefaultRoborazziOptions = RoborazziOptions( - compareOptions = CompareOptions(changeThreshold = 0f), // Pixel-perfect matching - recordOptions = RecordOptions(resizeScale = 0.5), // Reduce the size of the PNGs + // Pixel-perfect matching + compareOptions = CompareOptions(changeThreshold = 0f), + // Reduce the size of the PNGs + recordOptions = RecordOptions(resizeScale = 0.5), ) enum class DefaultTestDevices(val description: String, val spec: String) { diff --git a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt index de4aec9d7..114e845d4 100644 --- a/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt +++ b/core/ui/src/main/kotlin/com/google/samples/apps/nowinandroid/core/ui/NewsResourceCard.kt @@ -186,7 +186,8 @@ fun NewsResourceHeaderImage( painterResource(drawable.ic_placeholder_default) }, // TODO b/226661685: Investigate using alt text of image to populate content description - contentDescription = null, // decorative image, + // decorative image, + contentDescription = null, ) } } @@ -295,7 +296,8 @@ fun NewsResourceTopics( modifier: Modifier = Modifier, ) { Row( - modifier = modifier.horizontalScroll(rememberScrollState()), // causes narrow chips + // causes narrow chips + modifier = modifier.horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(4.dp), ) { for (followableTopic in topics) { diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index 65b5ecbc4..f0899b448 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -437,7 +437,8 @@ fun TopicIcon( DynamicAsyncImage( placeholder = painterResource(R.drawable.ic_icon_placeholder), imageUrl = imageUrl, - contentDescription = null, // decorative + // decorative + contentDescription = null, modifier = modifier .padding(10.dp) .size(32.dp), 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 7456ba92b..a9e329e9c 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 @@ -99,7 +99,8 @@ private fun InterestsIcon(topicImageUrl: String, modifier: Modifier = Modifier) .background(MaterialTheme.colorScheme.surface) .padding(4.dp), imageVector = NiaIcons.Person, - contentDescription = null, // decorative image + // decorative image + contentDescription = null, ) } else { DynamicAsyncImage( 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 65b65f61d..72842ec61 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 @@ -328,7 +328,8 @@ private fun SearchResultBody( topics.forEach { followableTopic -> val topicId = followableTopic.topic.id item( - key = "topic-$topicId", // Append a prefix to distinguish a key for news resources + // Append a prefix to distinguish a key for news resources + key = "topic-$topicId", span = StaggeredGridItemSpan.FullLine, ) { InterestsItem( diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index a06d659fa..f2e6114aa 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -39,7 +39,6 @@ rootProject { mapOf( "android" to "true", "ktlint_standard_property-naming" to "disabled", - "ktlint_standard_discouraged-comment-location" to "disabled", ), ) licenseHeaderFile(rootProject.file("spotless/copyright.kt")) From 32e1ec0447294ada76c83bc1f54a88f3487b0e04 Mon Sep 17 00:00:00 2001 From: lihenggui Date: Thu, 14 Dec 2023 16:18:27 -0800 Subject: [PATCH 34/71] Remove violations: property-naming --- .../apps/nowinandroid/navigation/NiaNavHost.kt | 4 ++-- .../samples/apps/nowinandroid/ui/NiaAppState.kt | 12 ++++++------ .../core/designsystem/component/Button.kt | 4 ++-- .../core/designsystem/component/Chip.kt | 14 +++++++------- .../core/designsystem/component/IconButton.kt | 4 ++-- .../core/designsystem/component/Tag.kt | 8 ++++---- .../bookmarks/navigation/BookmarksNavigation.kt | 6 +++--- .../feature/foryou/navigation/ForYouNavigation.kt | 6 +++--- .../interests/navigation/InterestsNavigation.kt | 6 +++--- .../feature/search/navigation/SearchNavigation.kt | 6 +++--- .../feature/topic/navigation/TopicNavigation.kt | 8 ++++---- .../feature/topic/TopicViewModelTest.kt | 4 ++-- gradle/init.gradle.kts | 1 - .../sync/initializers/SyncInitializer.kt | 4 ++-- .../sync/status/WorkManagerSyncManager.kt | 6 +++--- 15 files changed, 46 insertions(+), 47 deletions(-) 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 3d58ed5a6..70651ed12 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 @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen -import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouNavigationRoute +import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_NAVIGATION_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.search.navigation.searchScreen @@ -41,7 +41,7 @@ fun NiaNavHost( appState: NiaAppState, onShowSnackbar: suspend (String, String?) -> Boolean, modifier: Modifier = Modifier, - startDestination: String = forYouNavigationRoute, + startDestination: String = FOR_YOU_NAVIGATION_ROUTE, ) { val navController = appState.navController NavHost( 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 09e70069e..9c587cc5c 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 @@ -33,11 +33,11 @@ import androidx.tracing.trace import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank -import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksRoute +import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BOOKMARKS_ROUTE import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigateToBookmarks -import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouNavigationRoute +import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_NAVIGATION_ROUTE import com.google.samples.apps.nowinandroid.feature.foryou.navigation.navigateToForYou -import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsRoute +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.search.navigation.navigateToSearch import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination @@ -91,9 +91,9 @@ class NiaAppState( val currentTopLevelDestination: TopLevelDestination? @Composable get() = when (currentDestination?.route) { - forYouNavigationRoute -> FOR_YOU - bookmarksRoute -> BOOKMARKS - interestsRoute -> INTERESTS + FOR_YOU_NAVIGATION_ROUTE -> FOR_YOU + BOOKMARKS_ROUTE -> BOOKMARKS + INTERESTS_ROUTE -> INTERESTS else -> null } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt index a5ecc86c1..d490ff13e 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Button.kt @@ -134,7 +134,7 @@ fun NiaOutlinedButton( MaterialTheme.colorScheme.outline } else { MaterialTheme.colorScheme.onSurface.copy( - alpha = NiaButtonDefaults.DisabledOutlinedButtonBorderAlpha, + alpha = NiaButtonDefaults.DISABLED_OUTLINED_BUTTON_BORDER_ALPHA, ) }, ), @@ -315,7 +315,7 @@ fun NiaButtonLeadingIconPreview() { object NiaButtonDefaults { // TODO: File bug // OutlinedButton border color doesn't respect disabled state by default - const val DisabledOutlinedButtonBorderAlpha = 0.12f + const val DISABLED_OUTLINED_BUTTON_BORDER_ALPHA = 0.12f // TODO: File bug // OutlinedButton default border width isn't exposed via ButtonDefaults diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Chip.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Chip.kt index d1b7d124d..106f0b839 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Chip.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Chip.kt @@ -76,10 +76,10 @@ fun NiaFilterChip( borderColor = MaterialTheme.colorScheme.onBackground, selectedBorderColor = MaterialTheme.colorScheme.onBackground, disabledBorderColor = MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaChipDefaults.DisabledChipContentAlpha, + alpha = NiaChipDefaults.DISABLED_CHIP_CONTENT_ALPHA, ), disabledSelectedBorderColor = MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaChipDefaults.DisabledChipContentAlpha, + alpha = NiaChipDefaults.DISABLED_CHIP_CONTENT_ALPHA, ), selectedBorderWidth = NiaChipDefaults.ChipBorderWidth, ), @@ -88,16 +88,16 @@ fun NiaFilterChip( iconColor = MaterialTheme.colorScheme.onBackground, disabledContainerColor = if (selected) { MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaChipDefaults.DisabledChipContainerAlpha, + alpha = NiaChipDefaults.DISABLED_CHIP_CONTAINER_ALPHA, ) } else { Color.Transparent }, disabledLabelColor = MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaChipDefaults.DisabledChipContentAlpha, + alpha = NiaChipDefaults.DISABLED_CHIP_CONTENT_ALPHA, ), disabledLeadingIconColor = MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaChipDefaults.DisabledChipContentAlpha, + alpha = NiaChipDefaults.DISABLED_CHIP_CONTENT_ALPHA, ), selectedContainerColor = MaterialTheme.colorScheme.primaryContainer, selectedLabelColor = MaterialTheme.colorScheme.onBackground, @@ -124,7 +124,7 @@ fun ChipPreview() { object NiaChipDefaults { // TODO: File bug // FilterChip default values aren't exposed via FilterChipDefaults - const val DisabledChipContainerAlpha = 0.12f - const val DisabledChipContentAlpha = 0.38f + const val DISABLED_CHIP_CONTAINER_ALPHA = 0.12f + const val DISABLED_CHIP_CONTENT_ALPHA = 0.38f val ChipBorderWidth = 1.dp } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/IconButton.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/IconButton.kt index 503342d30..43ec11f0b 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/IconButton.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/IconButton.kt @@ -60,7 +60,7 @@ fun NiaIconToggleButton( checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer, disabledContainerColor = if (checked) { MaterialTheme.colorScheme.onBackground.copy( - alpha = NiaIconButtonDefaults.DisabledIconButtonContainerAlpha, + alpha = NiaIconButtonDefaults.DISABLED_ICON_BUTTON_CONTAINER_ALPHA, ) } else { Color.Transparent @@ -123,5 +123,5 @@ fun IconButtonPreviewUnchecked() { object NiaIconButtonDefaults { // TODO: File bug // IconToggleButton disabled container alpha not exposed by IconButtonDefaults - const val DisabledIconButtonContainerAlpha = 0.12f + const val DISABLED_ICON_BUTTON_CONTAINER_ALPHA = 0.12f } diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Tag.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Tag.kt index 8ca1588a8..290845936 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Tag.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/Tag.kt @@ -40,7 +40,7 @@ fun NiaTopicTag( MaterialTheme.colorScheme.primaryContainer } else { MaterialTheme.colorScheme.surfaceVariant.copy( - alpha = NiaTagDefaults.UnfollowedTopicTagContainerAlpha, + alpha = NiaTagDefaults.UNFOLLOWED_TOPIC_TAG_CONTAINER_ALPHA, ) } TextButton( @@ -50,7 +50,7 @@ fun NiaTopicTag( containerColor = containerColor, contentColor = contentColorFor(backgroundColor = containerColor), disabledContainerColor = MaterialTheme.colorScheme.onSurface.copy( - alpha = NiaTagDefaults.DisabledTopicTagContainerAlpha, + alpha = NiaTagDefaults.DISABLED_TOPIC_TAG_CONTAINER_ALPHA, ), ), ) { @@ -75,9 +75,9 @@ fun TagPreview() { * Now in Android tag default values. */ object NiaTagDefaults { - const val UnfollowedTopicTagContainerAlpha = 0.5f + const val UNFOLLOWED_TOPIC_TAG_CONTAINER_ALPHA = 0.5f // TODO: File bug // Button disabled container alpha value not exposed by ButtonDefaults - const val DisabledTopicTagContainerAlpha = 0.12f + const val DISABLED_TOPIC_TAG_CONTAINER_ALPHA = 0.12f } diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt index ebcde4ab1..81fa114e2 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt @@ -22,17 +22,17 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute -const val bookmarksRoute = "bookmarks_route" +const val BOOKMARKS_ROUTE = "bookmarks_route" fun NavController.navigateToBookmarks(navOptions: NavOptions? = null) { - this.navigate(bookmarksRoute, navOptions) + this.navigate(BOOKMARKS_ROUTE, navOptions) } fun NavGraphBuilder.bookmarksScreen( onTopicClick: (String) -> Unit, onShowSnackbar: suspend (String, String?) -> Boolean, ) { - composable(route = bookmarksRoute) { + composable(route = BOOKMARKS_ROUTE) { BookmarksRoute(onTopicClick, onShowSnackbar) } } diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index 705495cc2..264345be4 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -26,17 +26,17 @@ import androidx.navigation.navDeepLink import com.google.samples.apps.nowinandroid.feature.foryou.ForYouRoute const val LINKED_NEWS_RESOURCE_ID = "linkedNewsResourceId" -const val forYouNavigationRoute = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" +const val FOR_YOU_NAVIGATION_ROUTE = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" private const val DEEP_LINK_URI_PATTERN = "https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}" fun NavController.navigateToForYou(navOptions: NavOptions? = null) { - this.navigate(forYouNavigationRoute, navOptions) + this.navigate(FOR_YOU_NAVIGATION_ROUTE, navOptions) } fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { composable( - route = forYouNavigationRoute, + route = FOR_YOU_NAVIGATION_ROUTE, deepLinks = listOf( navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN }, ), 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 7558ec48d..831247e27 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 @@ -24,7 +24,7 @@ import androidx.navigation.navigation import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute private const val INTERESTS_GRAPH_ROUTE_PATTERN = "interests_graph" -const val interestsRoute = "interests_route" +const val INTERESTS_ROUTE = "interests_route" fun NavController.navigateToInterestsGraph(navOptions: NavOptions? = null) { this.navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions) @@ -36,9 +36,9 @@ fun NavGraphBuilder.interestsGraph( ) { navigation( route = INTERESTS_GRAPH_ROUTE_PATTERN, - startDestination = interestsRoute, + startDestination = INTERESTS_ROUTE, ) { - composable(route = interestsRoute) { + composable(route = INTERESTS_ROUTE) { InterestsRoute(onTopicClick) } nestedGraphs() 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 42bf3f475..a449600b2 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 @@ -22,10 +22,10 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.google.samples.apps.nowinandroid.feature.search.SearchRoute -const val searchRoute = "search_route" +const val SEARCH_ROUTE = "search_route" fun NavController.navigateToSearch(navOptions: NavOptions? = null) { - this.navigate(searchRoute, navOptions) + this.navigate(SEARCH_ROUTE, navOptions) } fun NavGraphBuilder.searchScreen( @@ -35,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 = searchRoute) { + composable(route = SEARCH_ROUTE) { SearchRoute( onBackClick = onBackClick, onInterestsClick = onInterestsClick, 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 c29b57d47..8052f766e 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 @@ -31,11 +31,11 @@ import kotlin.text.Charsets.UTF_8 private val URL_CHARACTER_ENCODING = UTF_8.name() @VisibleForTesting -internal const val topicIdArg = "topicId" +internal const val TOPIC_ID_ARG = "topicId" internal class TopicArgs(val topicId: String) { constructor(savedStateHandle: SavedStateHandle) : - this(URLDecoder.decode(checkNotNull(savedStateHandle[topicIdArg]), URL_CHARACTER_ENCODING)) + this(URLDecoder.decode(checkNotNull(savedStateHandle[TOPIC_ID_ARG]), URL_CHARACTER_ENCODING)) } fun NavController.navigateToTopic(topicId: String) { @@ -50,9 +50,9 @@ fun NavGraphBuilder.topicScreen( onTopicClick: (String) -> Unit, ) { composable( - route = "topic_route/{$topicIdArg}", + route = "topic_route/{$TOPIC_ID_ARG}", arguments = listOf( - navArgument(topicIdArg) { type = NavType.StringType }, + navArgument(TOPIC_ID_ARG) { type = NavType.StringType }, ), ) { TopicRoute(onBackClick = onBackClick, onTopicClick = onTopicClick) diff --git a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt index 8c6253d60..a9c9d96dc 100644 --- a/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt +++ b/feature/topic/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/topic/TopicViewModelTest.kt @@ -25,7 +25,7 @@ import com.google.samples.apps.nowinandroid.core.testing.repository.TestNewsRepo import com.google.samples.apps.nowinandroid.core.testing.repository.TestTopicsRepository import com.google.samples.apps.nowinandroid.core.testing.repository.TestUserDataRepository import com.google.samples.apps.nowinandroid.core.testing.util.MainDispatcherRule -import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicIdArg +import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ID_ARG import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @@ -60,7 +60,7 @@ class TopicViewModelTest { @Before fun setup() { viewModel = TopicViewModel( - savedStateHandle = SavedStateHandle(mapOf(topicIdArg to testInputTopics[0].topic.id)), + savedStateHandle = SavedStateHandle(mapOf(TOPIC_ID_ARG to testInputTopics[0].topic.id)), userDataRepository = userDataRepository, topicsRepository = topicsRepository, userNewsResourceRepository = userNewsResourceRepository, diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts index f2e6114aa..fe79fa01e 100644 --- a/gradle/init.gradle.kts +++ b/gradle/init.gradle.kts @@ -38,7 +38,6 @@ rootProject { ktlint(ktlintVersion).editorConfigOverride( mapOf( "android" to "true", - "ktlint_standard_property-naming" to "disabled", ), ) licenseHeaderFile(rootProject.file("spotless/copyright.kt")) diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/initializers/SyncInitializer.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/initializers/SyncInitializer.kt index 00f61f17d..0a631534b 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/initializers/SyncInitializer.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/initializers/SyncInitializer.kt @@ -28,7 +28,7 @@ object Sync { WorkManager.getInstance(context).apply { // Run sync on app startup and ensure only one sync worker runs at any time enqueueUniqueWork( - SyncWorkName, + SYNC_WORK_NAME, ExistingWorkPolicy.KEEP, SyncWorker.startUpSyncWork(), ) @@ -37,4 +37,4 @@ object Sync { } // This name should not be changed otherwise the app may have concurrent sync requests running -internal const val SyncWorkName = "SyncWorkName" +internal const val SYNC_WORK_NAME = "SyncWorkName" diff --git a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt index 9131e4888..1d251588e 100644 --- a/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt +++ b/sync/work/src/main/kotlin/com/google/samples/apps/nowinandroid/sync/status/WorkManagerSyncManager.kt @@ -22,7 +22,7 @@ import androidx.work.WorkInfo import androidx.work.WorkInfo.State import androidx.work.WorkManager import com.google.samples.apps.nowinandroid.core.data.util.SyncManager -import com.google.samples.apps.nowinandroid.sync.initializers.SyncWorkName +import com.google.samples.apps.nowinandroid.sync.initializers.SYNC_WORK_NAME import com.google.samples.apps.nowinandroid.sync.workers.SyncWorker import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow @@ -37,7 +37,7 @@ class WorkManagerSyncManager @Inject constructor( @ApplicationContext private val context: Context, ) : SyncManager { override val isSyncing: Flow = - WorkManager.getInstance(context).getWorkInfosForUniqueWorkFlow(SyncWorkName) + WorkManager.getInstance(context).getWorkInfosForUniqueWorkFlow(SYNC_WORK_NAME) .map(List::anyRunning) .conflate() @@ -45,7 +45,7 @@ class WorkManagerSyncManager @Inject constructor( val workManager = WorkManager.getInstance(context) // Run sync on app startup and ensure only one sync worker runs at any time workManager.enqueueUniqueWork( - SyncWorkName, + SYNC_WORK_NAME, ExistingWorkPolicy.KEEP, SyncWorker.startUpSyncWork(), ) From 83764082ffb7871d175cd915197a309f837797b5 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sat, 16 Dec 2023 10:50:38 +0000 Subject: [PATCH 35/71] Move app screenshot tests to match dependencies declaration Fixes #990 --- .../ui/NiaAppScreenSizesScreenshotTests.kt | 2 +- ...ompactWidth_compactHeight_showsNavigationBar.png | Bin ...mpactWidth_expandedHeight_showsNavigationBar.png | Bin ...compactWidth_mediumHeight_showsNavigationBar.png | Bin ...andedWidth_compactHeight_showsNavigationRail.png | Bin ...ndedWidth_expandedHeight_showsNavigationRail.png | Bin ...pandedWidth_mediumHeight_showsNavigationRail.png | Bin ...ediumWidth_compactHeight_showsNavigationRail.png | Bin ...diumWidth_expandedHeight_showsNavigationRail.png | Bin ...mediumWidth_mediumHeight_showsNavigationRail.png | Bin 10 files changed, 1 insertion(+), 1 deletion(-) rename app/src/{testDemo => testDemoDebug}/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt (99%) rename app/src/{testDemo => testDemoDebug}/screenshots/compactWidth_compactHeight_showsNavigationBar.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/compactWidth_expandedHeight_showsNavigationBar.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/compactWidth_mediumHeight_showsNavigationBar.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/expandedWidth_compactHeight_showsNavigationRail.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/mediumWidth_compactHeight_showsNavigationRail.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png (100%) rename app/src/{testDemo => testDemoDebug}/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png (100%) diff --git a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt similarity index 99% rename from app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt rename to app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index dcbc1e5c0..63a7c7f91 100644 --- a/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -154,7 +154,7 @@ class NiaAppScreenSizesScreenshotTests { composeTestRule.onRoot() .captureRoboImage( - "src/testDemo/screenshots/$screenshotName.png", + "src/testDemoDebug/screenshots/$screenshotName.png", roborazziOptions = DefaultRoborazziOptions, ) } diff --git a/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png b/app/src/testDemoDebug/screenshots/compactWidth_compactHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png rename to app/src/testDemoDebug/screenshots/compactWidth_compactHeight_showsNavigationBar.png diff --git a/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png b/app/src/testDemoDebug/screenshots/compactWidth_expandedHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png rename to app/src/testDemoDebug/screenshots/compactWidth_expandedHeight_showsNavigationBar.png diff --git a/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png b/app/src/testDemoDebug/screenshots/compactWidth_mediumHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png rename to app/src/testDemoDebug/screenshots/compactWidth_mediumHeight_showsNavigationBar.png diff --git a/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/expandedWidth_compactHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/expandedWidth_compactHeight_showsNavigationRail.png diff --git a/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png diff --git a/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png diff --git a/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/mediumWidth_compactHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/mediumWidth_compactHeight_showsNavigationRail.png diff --git a/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png diff --git a/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemoDebug/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png rename to app/src/testDemoDebug/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png From f9a498caf6b5f67c8646813be21ad757ff182ff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 08:44:06 +0000 Subject: [PATCH 36/71] Bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Build.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index d47342c6a..791cf6127 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -91,14 +91,14 @@ jobs: -x collectProdNonMinifiedBenchmarkBaselineProfile - name: Upload build outputs (APKs) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: APKs path: '**/build/outputs/apk/**/*.apk' - name: Upload test results (XML) if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results path: '**/build/test-results/test*UnitTest/**.xml' @@ -108,7 +108,7 @@ jobs: - name: Upload lint reports (HTML) if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: lint-reports path: '**/build/reports/lint-results-*.html' @@ -154,7 +154,7 @@ jobs: - name: Upload test reports if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-reports-${{ matrix.api-level }} path: '**/build/reports/androidTests' From 28bb3f1b2fe56e0842eed15aa1f43243afdc634f Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 19 Dec 2023 13:46:40 +0000 Subject: [PATCH 37/71] Add comment explaining why the launcher icon colors differ per build variant Change-Id: I30126402370162ccbe53e177652583e4472f2030 --- app/src/benchmark/res/values-night/colors.xml | 2 ++ app/src/benchmark/res/values/colors.xml | 2 ++ app/src/debug/res/values-night/colors.xml | 2 ++ app/src/debug/res/values/colors.xml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/app/src/benchmark/res/values-night/colors.xml b/app/src/benchmark/res/values-night/colors.xml index 677eb4e03..7e62bc52c 100644 --- a/app/src/benchmark/res/values-night/colors.xml +++ b/app/src/benchmark/res/values-night/colors.xml @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> + #FFFFFF #FF006780 diff --git a/app/src/benchmark/res/values/colors.xml b/app/src/benchmark/res/values/colors.xml index d33b7ba72..8524b17d2 100644 --- a/app/src/benchmark/res/values/colors.xml +++ b/app/src/benchmark/res/values/colors.xml @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> + #000000 #FF006780 diff --git a/app/src/debug/res/values-night/colors.xml b/app/src/debug/res/values-night/colors.xml index d6a4c98e0..2d36bde01 100644 --- a/app/src/debug/res/values-night/colors.xml +++ b/app/src/debug/res/values-night/colors.xml @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> + #FFFFFF #FFA23F16 diff --git a/app/src/debug/res/values/colors.xml b/app/src/debug/res/values/colors.xml index 6365ddb3f..56a856913 100644 --- a/app/src/debug/res/values/colors.xml +++ b/app/src/debug/res/values/colors.xml @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> + #000000 #FFA23F16 From e6b5560f078e371fef4513848f833b0087fffd63 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 19 Dec 2023 14:59:53 +0000 Subject: [PATCH 38/71] Revert "Move app screenshot tests to match dependencies declaration" --- .../ui/NiaAppScreenSizesScreenshotTests.kt | 2 +- ...ompactWidth_compactHeight_showsNavigationBar.png | Bin ...mpactWidth_expandedHeight_showsNavigationBar.png | Bin ...compactWidth_mediumHeight_showsNavigationBar.png | Bin ...andedWidth_compactHeight_showsNavigationRail.png | Bin ...ndedWidth_expandedHeight_showsNavigationRail.png | Bin ...pandedWidth_mediumHeight_showsNavigationRail.png | Bin ...ediumWidth_compactHeight_showsNavigationRail.png | Bin ...diumWidth_expandedHeight_showsNavigationRail.png | Bin ...mediumWidth_mediumHeight_showsNavigationRail.png | Bin 10 files changed, 1 insertion(+), 1 deletion(-) rename app/src/{testDemoDebug => testDemo}/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt (99%) rename app/src/{testDemoDebug => testDemo}/screenshots/compactWidth_compactHeight_showsNavigationBar.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/compactWidth_expandedHeight_showsNavigationBar.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/compactWidth_mediumHeight_showsNavigationBar.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/expandedWidth_compactHeight_showsNavigationRail.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/mediumWidth_compactHeight_showsNavigationRail.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png (100%) rename app/src/{testDemoDebug => testDemo}/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png (100%) diff --git a/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt similarity index 99% rename from app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt rename to app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt index 63a7c7f91..dcbc1e5c0 100644 --- a/app/src/testDemoDebug/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt +++ b/app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt @@ -154,7 +154,7 @@ class NiaAppScreenSizesScreenshotTests { composeTestRule.onRoot() .captureRoboImage( - "src/testDemoDebug/screenshots/$screenshotName.png", + "src/testDemo/screenshots/$screenshotName.png", roborazziOptions = DefaultRoborazziOptions, ) } diff --git a/app/src/testDemoDebug/screenshots/compactWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemoDebug/screenshots/compactWidth_compactHeight_showsNavigationBar.png rename to app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png diff --git a/app/src/testDemoDebug/screenshots/compactWidth_expandedHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemoDebug/screenshots/compactWidth_expandedHeight_showsNavigationBar.png rename to app/src/testDemo/screenshots/compactWidth_expandedHeight_showsNavigationBar.png diff --git a/app/src/testDemoDebug/screenshots/compactWidth_mediumHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png similarity index 100% rename from app/src/testDemoDebug/screenshots/compactWidth_mediumHeight_showsNavigationBar.png rename to app/src/testDemo/screenshots/compactWidth_mediumHeight_showsNavigationBar.png diff --git a/app/src/testDemoDebug/screenshots/expandedWidth_compactHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/expandedWidth_compactHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/expandedWidth_compactHeight_showsNavigationRail.png diff --git a/app/src/testDemoDebug/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/expandedWidth_expandedHeight_showsNavigationRail.png diff --git a/app/src/testDemoDebug/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/expandedWidth_mediumHeight_showsNavigationRail.png diff --git a/app/src/testDemoDebug/screenshots/mediumWidth_compactHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/mediumWidth_compactHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/mediumWidth_compactHeight_showsNavigationRail.png diff --git a/app/src/testDemoDebug/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/mediumWidth_expandedHeight_showsNavigationRail.png diff --git a/app/src/testDemoDebug/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png b/app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png similarity index 100% rename from app/src/testDemoDebug/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png rename to app/src/testDemo/screenshots/mediumWidth_mediumHeight_showsNavigationRail.png From d711a1a4dd08a5a36a2731c7617a995aa4559a36 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 19 Dec 2023 15:50:56 +0000 Subject: [PATCH 39/71] Update README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b71427dfe..6f13f5de2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The app is currently in development. The `prodRelease` variant is [available on **Now in Android** displays content from the [Now in Android](https://developer.android.com/series/now-in-android) series. Users can browse for links to recent videos, articles and other content. Users can also follow topics they are interested -in. +in, and be notified when new content is published which matches interests they are following. ## Screenshots @@ -109,12 +109,42 @@ Examples: manipulate the state of the `Test` repository and verify the resulting behavior, instead of checking that specific repository methods were called. -## Screenshot tests +To run the tests execute the following gradle tasks: + +- `testDemoDebug` run all local tests against the `demoDebug` variant. +- `connectedDemoDebugAndroidTest` run all instrumented tests against the `demoDebug` variant. -**Now In Android** uses [Roborazzi](https://github.com/takahirom/roborazzi) to do screenshot tests -of certain screens and components. To run these tests, run the `verifyRoborazziDemoDebug` or -`recordRoborazziDemoDebug` tasks. Note that screenshots are recorded on CI, using Linux, and other -platforms might generate slightly different images, making the tests fail. +**Note:** You should not run `./gradlew test` or `./gradlew connectedAndroidTest` as this will execute +tests against _all_ build variants which is both unecessary and will result in failures as only the +`demoDebug` variant is supported. No other variants have any tests (although this might change in future). + +## Screenshot tests +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) +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)). + +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: + +- `verifyRoborazziDemoDebug` run all screenshot tests, verifying the screenshots against the known +correct screenshots. +- `recordRoborazziDemoDebug` record new "known correct" screenshots. Use this command when you have +made changes to the UI and manually verified that they are rendered correctly. Screenshots will be +stored in `modulename/src/test/screenshots`. +- `compareRoborazziDemoDebug` create comparison images between failed tests and the known correct +images. These can also be found in `modulename/src/test/screenshots`. + +**Note:** The known correct screenshots stored in this repository are recorded on CI using Linux. Other +platforms may (and probably will) generate slightly different images, making the screenshot tests fail. +When working on a non-Linux platform, a workaround to this is to run `recordRoborazziDemoDebug` on the +`main` branch before starting work. After making changes, `verifyRoborazziDemoDebug` will identify only +legitimate changes. + +For more information about screenshot testing +[check out this talk](https://www.droidcon.com/2023/11/15/easy-screenshot-testing-with-compose/). # UI The app was designed using [Material 3 guidelines](https://m3.material.io/). Learn more about the design process and From a49e901fb419d179387382e72f927ef3363dfe8e Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 19 Dec 2023 16:02:35 +0000 Subject: [PATCH 40/71] Don't run unit tests on prodDebug variant Change-Id: I0adf6c70b9634de876aed64400a7d3b4033983b3 --- .github/workflows/Build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index d47342c6a..d1d631117 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -75,7 +75,7 @@ jobs: # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots. - name: Run local tests if: always() - run: ./gradlew testDemoDebug testProdDebug :lint:test + run: ./gradlew testDemoDebug :lint:test # Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when # https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a # release build From d018e57f526d38d8de8a6a7798eda25742cc93a6 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Tue, 19 Dec 2023 16:05:36 +0000 Subject: [PATCH 41/71] Fix spotless Change-Id: I5dea4fec5b391e8cb7794af13e99978fcb622fbb --- app/src/benchmark/res/values-night/colors.xml | 2 -- app/src/benchmark/res/values/colors.xml | 2 -- app/src/debug/res/values-night/colors.xml | 2 -- app/src/debug/res/values/colors.xml | 2 -- 4 files changed, 8 deletions(-) diff --git a/app/src/benchmark/res/values-night/colors.xml b/app/src/benchmark/res/values-night/colors.xml index 7e62bc52c..677eb4e03 100644 --- a/app/src/benchmark/res/values-night/colors.xml +++ b/app/src/benchmark/res/values-night/colors.xml @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - #FFFFFF #FF006780 diff --git a/app/src/benchmark/res/values/colors.xml b/app/src/benchmark/res/values/colors.xml index 8524b17d2..d33b7ba72 100644 --- a/app/src/benchmark/res/values/colors.xml +++ b/app/src/benchmark/res/values/colors.xml @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - #000000 #FF006780 diff --git a/app/src/debug/res/values-night/colors.xml b/app/src/debug/res/values-night/colors.xml index 2d36bde01..d6a4c98e0 100644 --- a/app/src/debug/res/values-night/colors.xml +++ b/app/src/debug/res/values-night/colors.xml @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - #FFFFFF #FFA23F16 diff --git a/app/src/debug/res/values/colors.xml b/app/src/debug/res/values/colors.xml index 56a856913..6365ddb3f 100644 --- a/app/src/debug/res/values/colors.xml +++ b/app/src/debug/res/values/colors.xml @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - #000000 #FFA23F16 From 2a2d8ed88df563e22e82b0b53efa351850f3c629 Mon Sep 17 00:00:00 2001 From: lihenggui Date: Tue, 19 Dec 2023 10:17:42 -0800 Subject: [PATCH 42/71] Change the name of FOR_YOU_NAVIGATION_ROUTE to FOR_YOU_ROUTE --- .../samples/apps/nowinandroid/navigation/NiaNavHost.kt | 4 ++-- .../com/google/samples/apps/nowinandroid/ui/NiaAppState.kt | 4 ++-- .../feature/foryou/navigation/ForYouNavigation.kt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) 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 70651ed12..6167b0b59 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 @@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier 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_NAVIGATION_ROUTE +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.search.navigation.searchScreen @@ -41,7 +41,7 @@ fun NiaNavHost( appState: NiaAppState, onShowSnackbar: suspend (String, String?) -> Boolean, modifier: Modifier = Modifier, - startDestination: String = FOR_YOU_NAVIGATION_ROUTE, + startDestination: String = FOR_YOU_ROUTE, ) { val navController = appState.navController NavHost( 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 9c587cc5c..b99eab245 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 @@ -35,7 +35,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BOOKMARKS_ROUTE import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigateToBookmarks -import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_NAVIGATION_ROUTE +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 @@ -91,7 +91,7 @@ class NiaAppState( val currentTopLevelDestination: TopLevelDestination? @Composable get() = when (currentDestination?.route) { - FOR_YOU_NAVIGATION_ROUTE -> FOR_YOU + FOR_YOU_ROUTE -> FOR_YOU BOOKMARKS_ROUTE -> BOOKMARKS INTERESTS_ROUTE -> INTERESTS else -> null diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index 264345be4..154b0f83b 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -26,17 +26,17 @@ import androidx.navigation.navDeepLink import com.google.samples.apps.nowinandroid.feature.foryou.ForYouRoute const val LINKED_NEWS_RESOURCE_ID = "linkedNewsResourceId" -const val FOR_YOU_NAVIGATION_ROUTE = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" +const val FOR_YOU_ROUTE = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" private const val DEEP_LINK_URI_PATTERN = "https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}" fun NavController.navigateToForYou(navOptions: NavOptions? = null) { - this.navigate(FOR_YOU_NAVIGATION_ROUTE, navOptions) + this.navigate(FOR_YOU_ROUTE, navOptions) } fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { composable( - route = FOR_YOU_NAVIGATION_ROUTE, + route = FOR_YOU_ROUTE, deepLinks = listOf( navDeepLink { uriPattern = DEEP_LINK_URI_PATTERN }, ), From d671126f9eb5532206db2c7260e3cc06a978dd77 Mon Sep 17 00:00:00 2001 From: lihenggui Date: Tue, 19 Dec 2023 11:15:18 -0800 Subject: [PATCH 43/71] Move @Suppress("ktlint:standard:max-line-length") to function scope --- .../fake/FakeNiaNetworkDataSourceTest.kt | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt b/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt index 147252cf1..a0c60fdcb 100644 --- a/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt +++ b/core/network/src/test/kotlin/com/google/samples/apps/nowinandroid/core/network/fake/FakeNiaNetworkDataSourceTest.kt @@ -44,47 +44,45 @@ class FakeNiaNetworkDataSourceTest { ) } + @Suppress("ktlint:standard:max-line-length") @Test - fun testDeserializationOfTopics() = - @Suppress("ktlint:standard:max-line-length") - runTest(testDispatcher) { - assertEquals( - NetworkTopic( - id = "1", - name = "Headlines", - shortDescription = "News you'll definitely be interested in", - longDescription = "The latest events and announcements from the world of Android development.", - url = "", - imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Headlines.svg?alt=media&token=506faab0-617a-4668-9e63-4a2fb996603f", - ), - subject.getTopics().first(), - ) - } + fun testDeserializationOfTopics() = runTest(testDispatcher) { + assertEquals( + NetworkTopic( + id = "1", + name = "Headlines", + shortDescription = "News you'll definitely be interested in", + longDescription = "The latest events and announcements from the world of Android development.", + url = "", + imageUrl = "https://firebasestorage.googleapis.com/v0/b/now-in-android.appspot.com/o/img%2Fic_topic_Headlines.svg?alt=media&token=506faab0-617a-4668-9e63-4a2fb996603f", + ), + subject.getTopics().first(), + ) + } + @Suppress("ktlint:standard:max-line-length") @Test - fun testDeserializationOfNewsResources() = - @Suppress("ktlint:standard:max-line-length") - runTest(testDispatcher) { - assertEquals( - NetworkNewsResource( - id = "125", - title = "Android Basics with Compose", - content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. ", - url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html", - headerImageUrl = "https://developer.android.com/images/hero-assets/android-basics-compose.svg", - publishDate = LocalDateTime( - year = 2022, - monthNumber = 5, - dayOfMonth = 4, - hour = 23, - minute = 0, - second = 0, - nanosecond = 0, - ).toInstant(TimeZone.UTC), - type = "Codelab", - topics = listOf("2", "3", "10"), - ), - subject.getNewsResources().find { it.id == "125" }, - ) - } + fun testDeserializationOfNewsResources() = runTest(testDispatcher) { + assertEquals( + NetworkNewsResource( + id = "125", + title = "Android Basics with Compose", + content = "We released the first two units of Android Basics with Compose, our first free course that teaches Android Development with Jetpack Compose to anyone; you do not need any prior programming experience other than basic computer literacy to get started. ", + url = "https://android-developers.googleblog.com/2022/05/new-android-basics-with-compose-course.html", + headerImageUrl = "https://developer.android.com/images/hero-assets/android-basics-compose.svg", + publishDate = LocalDateTime( + year = 2022, + monthNumber = 5, + dayOfMonth = 4, + hour = 23, + minute = 0, + second = 0, + nanosecond = 0, + ).toInstant(TimeZone.UTC), + type = "Codelab", + topics = listOf("2", "3", "10"), + ), + subject.getNewsResources().find { it.id == "125" }, + ) + } } From 485440b550c0a241e96fbe78e52632ed0ec94619 Mon Sep 17 00:00:00 2001 From: lihenggui Date: Tue, 19 Dec 2023 12:42:49 -0800 Subject: [PATCH 44/71] Suppress ktlint:standard:function-naming in ResultKtTest --- .../samples/apps/nowinandroid/core/result/ResultKtTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt index 512a1a2f2..08fed508c 100644 --- a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt +++ b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt @@ -24,8 +24,9 @@ import kotlin.test.assertEquals class ResultKtTest { + @Suppress("ktlint:standard:function-naming") @Test - fun result_catches_errors() = runTest { + fun Result_catches_errors() = runTest { flow { emit(1) throw Exception("Test Done") From 56fbe13b5abe497df8b32a262001a922f3f9921c Mon Sep 17 00:00:00 2001 From: lihenggui Date: Tue, 19 Dec 2023 14:16:13 -0800 Subject: [PATCH 45/71] Ignore function naming check when annotated with @Test --- .editorconfig | 2 +- .../samples/apps/nowinandroid/core/result/ResultKtTest.kt | 1 - .../core/datastore/IntToStringIdsMigrationTest.kt | 4 ++-- .../nowinandroid/core/datastore/ListToMapMigrationTest.kt | 6 +++--- .../nowinandroid/core/designsystem/TagScreenshotTests.kt | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2d5a46b3c..7be3f8784 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,4 +4,4 @@ [*.{kt,kts}] ij_kotlin_allow_trailing_comma=true ij_kotlin_allow_trailing_comma_on_call_site=true -ktlint_function_naming_ignore_when_annotated_with=Composable +ktlint_function_naming_ignore_when_annotated_with=Composable, Test diff --git a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt index 08fed508c..4f1229e9d 100644 --- a/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt +++ b/core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/result/ResultKtTest.kt @@ -24,7 +24,6 @@ import kotlin.test.assertEquals class ResultKtTest { - @Suppress("ktlint:standard:function-naming") @Test fun Result_catches_errors() = runTest { flow { diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt index 392ca2550..8b97cff34 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/IntToStringIdsMigrationTest.kt @@ -27,7 +27,7 @@ import kotlin.test.assertTrue class IntToStringIdsMigrationTest { @Test - fun intToStringIdsMigration_should_migrate_topic_ids() = runTest { + fun IntToStringIdsMigration_should_migrate_topic_ids() = runTest { // Set up existing preferences with topic int ids val preMigrationUserPreferences = userPreferences { deprecatedIntFollowedTopicIds.addAll(listOf(1, 2, 3)) @@ -56,7 +56,7 @@ class IntToStringIdsMigrationTest { } @Test - fun intToStringIdsMigration_should_migrate_author_ids() = runTest { + fun IntToStringIdsMigration_should_migrate_author_ids() = runTest { // Set up existing preferences with author int ids val preMigrationUserPreferences = userPreferences { deprecatedIntFollowedAuthorIds.addAll(listOf(4, 5, 6)) diff --git a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt index 41404d6a9..f7e083b45 100644 --- a/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt +++ b/core/datastore/src/test/kotlin/com/google/samples/apps/nowinandroid/core/datastore/ListToMapMigrationTest.kt @@ -24,7 +24,7 @@ import kotlin.test.assertTrue class ListToMapMigrationTest { @Test - fun listToMapMigration_should_migrate_topic_ids() = runTest { + fun ListToMapMigration_should_migrate_topic_ids() = runTest { // Set up existing preferences with topic ids val preMigrationUserPreferences = userPreferences { deprecatedFollowedTopicIds.addAll(listOf("1", "2", "3")) @@ -50,7 +50,7 @@ class ListToMapMigrationTest { } @Test - fun listToMapMigration_should_migrate_author_ids() = runTest { + fun ListToMapMigration_should_migrate_author_ids() = runTest { // Set up existing preferences with author ids val preMigrationUserPreferences = userPreferences { deprecatedFollowedAuthorIds.addAll(listOf("4", "5", "6")) @@ -76,7 +76,7 @@ class ListToMapMigrationTest { } @Test - fun listToMapMigration_should_migrate_bookmarks() = runTest { + fun ListToMapMigration_should_migrate_bookmarks() = runTest { // Set up existing preferences with bookmarks val preMigrationUserPreferences = userPreferences { deprecatedBookmarkedNewsResourceIds.addAll(listOf("7", "8", "9")) diff --git a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt index 16ed71aeb..d9edfd6c6 100644 --- a/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt +++ b/core/designsystem/src/test/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/TagScreenshotTests.kt @@ -47,7 +47,7 @@ class TagScreenshotTests() { val composeTestRule = createAndroidComposeRule() @Test - fun tag_multipleThemes() { + fun Tag_multipleThemes() { composeTestRule.captureMultiTheme("Tag") { NiaTopicTag(followed = true, onClick = {}) { Text("TOPIC") From 115e66b6e34950354bf3f28617d2c67674509b29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:18:00 +0000 Subject: [PATCH 46/71] Bump lint from 31.1.3 to 31.2.0 Bumps `lint` from 31.1.3 to 31.2.0. Updates `com.android.tools.lint:lint-api` from 31.1.3 to 31.2.0 Updates `com.android.tools.lint:lint-checks` from 31.1.3 to 31.2.0 Updates `com.android.tools.lint:lint-tests` from 31.1.3 to 31.2.0 --- updated-dependencies: - dependency-name: com.android.tools.lint:lint-api dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.tools.lint:lint-checks dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.tools.lint:lint-tests 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 95fb23428..7333db3dd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -47,7 +47,7 @@ kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" ksp = "1.9.10-1.0.13" -lint = "31.1.3" +lint = "31.2.0" okhttp = "4.12.0" protobuf = "3.24.4" protobufPlugin = "0.9.4" From f36af7c2102a15f73e467d931456e97532834c8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:18:46 +0000 Subject: [PATCH 47/71] Bump hilt from 2.49 to 2.50 Bumps `hilt` from 2.49 to 2.50. Updates `com.google.dagger:hilt-android` from 2.49 to 2.50 - [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.49...dagger-2.50) Updates `com.google.dagger:hilt-android-testing` from 2.49 to 2.50 - [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.49...dagger-2.50) Updates `com.google.dagger:hilt-android-compiler` from 2.49 to 2.50 - [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.49...dagger-2.50) Updates `com.google.dagger.hilt.android` from 2.49 to 2.50 - [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.49...dagger-2.50) --- 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 95fb23428..df5db8cd0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,7 +38,7 @@ firebasePerfPlugin = "1.4.2" gmsPlugin = "4.4.0" googleOss = "17.0.1" googleOssPlugin = "0.10.6" -hilt = "2.49" +hilt = "2.50" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" From 60c5f5ee264c9dc08b521bccbbef4bcad09f2f34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:19:42 +0000 Subject: [PATCH 48/71] Bump coil from 2.4.0 to 2.5.0 Bumps `coil` from 2.4.0 to 2.5.0. Updates `io.coil-kt:coil` from 2.4.0 to 2.5.0 - [Release notes](https://github.com/coil-kt/coil/releases) - [Changelog](https://github.com/coil-kt/coil/blob/main/CHANGELOG.md) - [Commits](https://github.com/coil-kt/coil/compare/2.4.0...2.5.0) Updates `io.coil-kt:coil-compose` from 2.4.0 to 2.5.0 - [Release notes](https://github.com/coil-kt/coil/releases) - [Changelog](https://github.com/coil-kt/coil/blob/main/CHANGELOG.md) - [Commits](https://github.com/coil-kt/coil/compare/2.4.0...2.5.0) Updates `io.coil-kt:coil-svg` from 2.4.0 to 2.5.0 - [Release notes](https://github.com/coil-kt/coil/releases) - [Changelog](https://github.com/coil-kt/coil/blob/main/CHANGELOG.md) - [Commits](https://github.com/coil-kt/coil/compare/2.4.0...2.5.0) --- updated-dependencies: - dependency-name: io.coil-kt:coil dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.coil-kt:coil-compose dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: io.coil-kt:coil-svg 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 95fb23428..6268707b8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ androidxTracing = "1.1.0" androidxUiAutomator = "2.2.0" androidxWindowManager = "1.1.0" androidxWork = "2.9.0-rc01" -coil = "2.4.0" +coil = "2.5.0" dependencyGuard = "0.4.3" firebaseBom = "32.4.0" firebaseCrashlyticsPlugin = "2.9.9" From e9f7cb820457c20f0f79f6e6e4c35434c16d4a45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:19:58 +0000 Subject: [PATCH 49/71] Bump room from 2.6.0 to 2.6.1 Bumps `room` from 2.6.0 to 2.6.1. Updates `androidx.room:room-compiler` from 2.6.0 to 2.6.1 Updates `androidx.room:room-ktx` from 2.6.0 to 2.6.1 Updates `androidx.room:room-runtime` from 2.6.0 to 2.6.1 --- updated-dependencies: - dependency-name: androidx.room:room-compiler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.room:room-ktx dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.room:room-runtime dependency-type: direct:production update-type: version-update:semver-patch ... 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 95fb23428..90e8bd2bd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,7 +55,7 @@ retrofit = "2.9.0" retrofitKotlinxSerializationJson = "1.0.0" robolectric = "4.11.1" roborazzi = "1.6.0" -room = "2.6.0" +room = "2.6.1" secrets = "2.0.1" truth = "1.1.5" turbine = "1.0.0" From a878e70f418a2aa88fe07a6e921a06dac42f8800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:20:43 +0000 Subject: [PATCH 50/71] Bump androidGradlePlugin from 8.1.3 to 8.2.0 Bumps `androidGradlePlugin` from 8.1.3 to 8.2.0. Updates `com.android.tools.build:gradle` from 8.1.3 to 8.2.0 Updates `com.android.application` from 8.1.3 to 8.2.0 Updates `com.android.library` from 8.1.3 to 8.2.0 Updates `com.android.test` from 8.1.3 to 8.2.0 --- updated-dependencies: - dependency-name: com.android.tools.build:gradle dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.application dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.library dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.android.test 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 95fb23428..15ea9b0c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ accompanist = "0.32.0" androidDesugarJdkLibs = "2.0.3" # AGP and tools should be updated together -androidGradlePlugin = "8.1.3" +androidGradlePlugin = "8.2.0" androidTools = "31.1.3" androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" From 334377e303e9708240729a575bf607697a23ad65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:20:59 +0000 Subject: [PATCH 51/71] Bump androidx.window:window from 1.1.0 to 1.2.0 Bumps androidx.window:window from 1.1.0 to 1.2.0. --- updated-dependencies: - dependency-name: androidx.window:window 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 95fb23428..2f529984d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ androidxTestRules = "1.5.0" androidxTestRunner = "1.5.2" androidxTracing = "1.1.0" androidxUiAutomator = "2.2.0" -androidxWindowManager = "1.1.0" +androidxWindowManager = "1.2.0" androidxWork = "2.9.0-rc01" coil = "2.4.0" dependencyGuard = "0.4.3" From 5ccf4f5ec61e1a4304d7ff86af95370989cee32a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:21:06 +0000 Subject: [PATCH 52/71] Bump com.android.tools:desugar_jdk_libs from 2.0.3 to 2.0.4 Bumps [com.android.tools:desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 2.0.3 to 2.0.4. - [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/desugar_jdk_libs/commits) --- updated-dependencies: - dependency-name: com.android.tools:desugar_jdk_libs dependency-type: direct:production update-type: version-update:semver-patch ... 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 95fb23428..94823336d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] accompanist = "0.32.0" -androidDesugarJdkLibs = "2.0.3" +androidDesugarJdkLibs = "2.0.4" # AGP and tools should be updated together androidGradlePlugin = "8.1.3" androidTools = "31.1.3" From 2dd917fd7de59803745cc10c7ddcf06b5a083a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:22:19 +0000 Subject: [PATCH 53/71] Bump the kotlin-ksp-compose group with 7 updates Bumps the kotlin-ksp-compose group with 7 updates: | Package | From | To | | --- | --- | --- | | androidx.compose.compiler:compiler | `1.5.3` | `1.5.7` | | [org.jetbrains.kotlin:kotlin-stdlib-jdk8](https://github.com/JetBrains/kotlin) | `1.9.10` | `1.9.21` | | [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) | `1.9.10` | `1.9.21` | | [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) | `1.9.10` | `1.9.21` | | [org.jetbrains.kotlin.plugin.serialization](https://github.com/JetBrains/kotlin) | `1.9.10` | `1.9.21` | | [com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin](https://github.com/google/ksp) | `1.9.10-1.0.13` | `1.9.21-1.0.16` | | [com.google.devtools.ksp](https://github.com/google/ksp) | `1.9.10-1.0.13` | `1.9.21-1.0.16` | Updates `androidx.compose.compiler:compiler` from 1.5.3 to 1.5.7 Updates `org.jetbrains.kotlin:kotlin-stdlib-jdk8` from 1.9.10 to 1.9.21 - [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.10...v1.9.21) Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 1.9.10 to 1.9.21 - [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.10...v1.9.21) Updates `org.jetbrains.kotlin.jvm` from 1.9.10 to 1.9.21 - [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.10...v1.9.21) Updates `org.jetbrains.kotlin.plugin.serialization` from 1.9.10 to 1.9.21 - [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.10...v1.9.21) Updates `com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin` from 1.9.10-1.0.13 to 1.9.21-1.0.16 - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/compare/1.9.10-1.0.13...1.9.21-1.0.16) Updates `com.google.devtools.ksp` from 1.9.10-1.0.13 to 1.9.21-1.0.16 - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/compare/1.9.10-1.0.13...1.9.21-1.0.16) --- updated-dependencies: - dependency-name: androidx.compose.compiler:compiler dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kotlin-ksp-compose - 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: 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 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 65ddba6bc..95f3c7827 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" androidxComposeBom = "2023.10.01" -androidxComposeCompiler = "1.5.3" +androidxComposeCompiler = "1.5.7" androidxComposeRuntimeTracing = "1.0.0-beta01" androidxCore = "1.12.0" androidxCoreSplashscreen = "1.0.1" @@ -42,11 +42,11 @@ hilt = "2.49" hiltExt = "1.1.0" jacoco = "0.8.7" junit4 = "4.13.2" -kotlin = "1.9.10" +kotlin = "1.9.21" kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" -ksp = "1.9.10-1.0.13" +ksp = "1.9.21-1.0.16" lint = "31.2.0" okhttp = "4.12.0" protobuf = "3.24.4" From b801a7deeae4a474bedfa12e95b817e7bc83c090 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Wed, 20 Dec 2023 18:55:34 +0000 Subject: [PATCH 54/71] Moving comments to _after_ first XML element Change-Id: Id4271a67ffcfbd712a2dd1f363970fcd31ee5ac0 --- app/src/benchmark/res/values-night/colors.xml | 2 ++ app/src/benchmark/res/values/colors.xml | 2 ++ app/src/debug/res/values-night/colors.xml | 2 ++ app/src/debug/res/values/colors.xml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/app/src/benchmark/res/values-night/colors.xml b/app/src/benchmark/res/values-night/colors.xml index 677eb4e03..cbf22c766 100644 --- a/app/src/benchmark/res/values-night/colors.xml +++ b/app/src/benchmark/res/values-night/colors.xml @@ -15,6 +15,8 @@ limitations under the License. --> + #FFFFFF #FF006780 diff --git a/app/src/benchmark/res/values/colors.xml b/app/src/benchmark/res/values/colors.xml index d33b7ba72..a98c6d8f6 100644 --- a/app/src/benchmark/res/values/colors.xml +++ b/app/src/benchmark/res/values/colors.xml @@ -15,6 +15,8 @@ limitations under the License. --> + #000000 #FF006780 diff --git a/app/src/debug/res/values-night/colors.xml b/app/src/debug/res/values-night/colors.xml index d6a4c98e0..daa017e4a 100644 --- a/app/src/debug/res/values-night/colors.xml +++ b/app/src/debug/res/values-night/colors.xml @@ -15,6 +15,8 @@ limitations under the License. --> + #FFFFFF #FFA23F16 diff --git a/app/src/debug/res/values/colors.xml b/app/src/debug/res/values/colors.xml index 6365ddb3f..487a7820b 100644 --- a/app/src/debug/res/values/colors.xml +++ b/app/src/debug/res/values/colors.xml @@ -15,6 +15,8 @@ limitations under the License. --> + #000000 #FFA23F16 From 57bf575514f2bb0e1c140e57b7c3e92a96b0457b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Dec 2023 19:03:28 +0000 Subject: [PATCH 55/71] Bump androidxWork from 2.9.0-rc01 to 2.9.0 Bumps `androidxWork` from 2.9.0-rc01 to 2.9.0. Updates `androidx.work:work-runtime-ktx` from 2.9.0-rc01 to 2.9.0 Updates `androidx.work:work-testing` from 2.8.1 to 2.9.0 --- updated-dependencies: - dependency-name: androidx.work:work-runtime-ktx dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: androidx.work:work-testing dependency-type: direct:production update-type: version-update:semver-minor ... 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 17a65460b..8f3ac5dd8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ androidxTestRunner = "1.5.2" androidxTracing = "1.1.0" androidxUiAutomator = "2.2.0" androidxWindowManager = "1.2.0" -androidxWork = "2.9.0-rc01" +androidxWork = "2.9.0" coil = "2.5.0" dependencyGuard = "0.4.3" firebaseBom = "32.4.0" @@ -152,7 +152,7 @@ firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "fir firebase-performance-gradlePlugin = { group = "com.google.firebase", name = "perf-plugin", version.ref = "firebasePerfPlugin" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } -work-testing = { group = "androidx.work", name = "work-testing", version = "2.8.1" } +work-testing = { group = "androidx.work", name = "work-testing", version = "2.9.0" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From bdb10a687079ca0fd684fcbd31f729c84a9cfc51 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 21 Dec 2023 18:45:05 +0100 Subject: [PATCH 56/71] Update core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt Co-authored-by: Don Turner --- .../core/designsystem/component/scrollbar/ScrollbarExt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt index 76fab08d0..3fcc8f2c0 100644 --- a/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt +++ b/core/designsystem/src/main/kotlin/com/google/samples/apps/nowinandroid/core/designsystem/component/scrollbar/ScrollbarExt.kt @@ -231,4 +231,4 @@ fun LazyStaggeredGridState.scrollbarState( } private inline fun List.floatSumOf(selector: (T) -> Float): Float = - fold(0f) { acc, it -> acc + selector(it) } + fold(initial = 0f) { accumulator, listItem -> accumulator + selector(listItem) } From 65f0d5b2d9cb1a743e91dbd7eb06423baf9c03a7 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 21 Dec 2023 18:48:05 +0100 Subject: [PATCH 57/71] Update feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt --- .../feature/foryou/navigation/ForYouNavigation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt index efbe67859..8e94a491a 100644 --- a/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt +++ b/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/navigation/ForYouNavigation.kt @@ -30,9 +30,7 @@ const val FOR_YOU_ROUTE = "for_you_route/{$LINKED_NEWS_RESOURCE_ID}" private const val DEEP_LINK_URI_PATTERN = "https://www.nowinandroid.apps.samples.google.com/foryou/{$LINKED_NEWS_RESOURCE_ID}" -fun NavController.navigateToForYou(navOptions: NavOptions) { - this.navigate(FOR_YOU_ROUTE, navOptions) -} +fun NavController.navigateToForYou(navOptions: NavOptions) = navigate(FOR_YOU_ROUTE, navOptions) fun NavGraphBuilder.forYouScreen(onTopicClick: (String) -> Unit) { composable( From 6cd5958665f6c54aeb082a4acac28967b6dc946a Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 21 Dec 2023 18:50:26 +0100 Subject: [PATCH 58/71] Update feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt --- .../feature/bookmarks/navigation/BookmarksNavigation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt index a62b2c5d1..13d0baef0 100644 --- a/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt +++ b/feature/bookmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/bookmarks/navigation/BookmarksNavigation.kt @@ -24,9 +24,7 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.BookmarksRoute const val BOOKMARKS_ROUTE = "bookmarks_route" -fun NavController.navigateToBookmarks(navOptions: NavOptions) { - this.navigate(BOOKMARKS_ROUTE, navOptions) -} +fun NavController.navigateToBookmarks(navOptions: NavOptions) = navigate(BOOKMARKS_ROUTE, navOptions) fun NavGraphBuilder.bookmarksScreen( onTopicClick: (String) -> Unit, From b7a628ece5852dadbd4e2c69711ebbe24bcf45fc Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 21 Dec 2023 18:50:50 +0100 Subject: [PATCH 59/71] Update feature/interests/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/interests/navigation/InterestsNavigation.kt --- .../feature/interests/navigation/InterestsNavigation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 d6ebaeeb7..2ad7c560b 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 @@ -26,9 +26,7 @@ import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute private const val INTERESTS_GRAPH_ROUTE_PATTERN = "interests_graph" const val INTERESTS_ROUTE = "interests_route" -fun NavController.navigateToInterestsGraph(navOptions: NavOptions) { - this.navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions) -} +fun NavController.navigateToInterestsGraph(navOptions: NavOptions) = navigate(INTERESTS_GRAPH_ROUTE_PATTERN, navOptions) fun NavGraphBuilder.interestsGraph( onTopicClick: (String) -> Unit, From cd62fd5e01ddf8902dd5c86bd2f0a3b368f8e34f Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Thu, 21 Dec 2023 18:51:17 +0100 Subject: [PATCH 60/71] Update feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/navigation/SearchNavigation.kt --- .../feature/search/navigation/SearchNavigation.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 a449600b2..81f3576b4 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 @@ -24,9 +24,7 @@ import com.google.samples.apps.nowinandroid.feature.search.SearchRoute const val SEARCH_ROUTE = "search_route" -fun NavController.navigateToSearch(navOptions: NavOptions? = null) { - this.navigate(SEARCH_ROUTE, navOptions) -} +fun NavController.navigateToSearch(navOptions: NavOptions? = null) = navigate(SEARCH_ROUTE, navOptions) fun NavGraphBuilder.searchScreen( onBackClick: () -> Unit, From be9dc533e36815ddbdeb448a69e641e9ca1687ab Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 22 Dec 2023 09:48:37 +0100 Subject: [PATCH 61/71] Replace `Enum.values()` with `Enum.entries` 'Enum.values()' is recommended to be replaced by 'Enum.entries' since 1.9 --- .../com/google/samples/apps/nowinandroid/ui/NiaAppState.kt | 2 +- .../interests/ScrollTopicListPowerMetricsBenchmark.kt | 2 +- .../apps/nowinandroid/core/testing/util/ScreenshotHelper.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 b99eab245..7a38b6649 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 @@ -115,7 +115,7 @@ class NiaAppState( * Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the * route. */ - val topLevelDestinations: List = TopLevelDestination.values().asList() + val topLevelDestinations: List = TopLevelDestination.entries /** * The top level destinations that have unread news resources. diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt index 13c6f55e3..f938fad62 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt @@ -45,7 +45,7 @@ class ScrollTopicListPowerMetricsBenchmark { @get:Rule val benchmarkRule = MacrobenchmarkRule() - private val categories = PowerCategory.values() + private val categories = PowerCategory.entries .associateWith { PowerCategoryDisplayLevel.TOTAL } @Test diff --git a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index 0f00ff16d..468fff8df 100644 --- a/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -53,7 +53,7 @@ fun AndroidComposeTestRule, A>.c screenshotName: String, body: @Composable () -> Unit, ) { - DefaultTestDevices.values().forEach { + DefaultTestDevices.entries.forEach { this.captureForDevice(it.description, it.spec, screenshotName, body = body) } } From a8450176ede6f51143563f56a94b210c27ef081e Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 22 Dec 2023 11:04:43 +0100 Subject: [PATCH 62/71] Add tests for `DesignSystemDetector` lint --- .../designsystem/DesignSystemDetectorTest.kt | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt diff --git a/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt b/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt new file mode 100644 index 000000000..6ea55c7e9 --- /dev/null +++ b/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.lint.designsystem + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin +import com.android.tools.lint.checks.infrastructure.TestLintTask.lint +import com.google.samples.apps.nowinandroid.lint.designsystem.DesignSystemDetector.Companion.ISSUE +import com.google.samples.apps.nowinandroid.lint.designsystem.DesignSystemDetector.Companion.METHOD_NAMES +import com.google.samples.apps.nowinandroid.lint.designsystem.DesignSystemDetector.Companion.RECEIVER_NAMES +import org.junit.Test + +class DesignSystemDetectorTest { + + @Test + fun `detect replacements of Composable`() { + lint() + .issues(ISSUE) + .allowMissingSdk() + .files( + COMPOSABLE_STUB, + STUBS, + kotlin( + """ + |import androidx.compose.runtime.Composable + | + |@Composable + |fun App() { + ${METHOD_NAMES.keys.joinToString("\n") { "| $it()" }} + |} + """.trimMargin(), + ).indented(), + ) + .run() + .expect( + """ + src/test.kt:5: Error: Using MaterialTheme instead of NiaTheme [DesignSystem] + MaterialTheme() + ~~~~~~~~~~~~~~~ + src/test.kt:6: Error: Using Button instead of NiaButton [DesignSystem] + Button() + ~~~~~~~~ + src/test.kt:7: Error: Using OutlinedButton instead of NiaOutlinedButton [DesignSystem] + OutlinedButton() + ~~~~~~~~~~~~~~~~ + src/test.kt:8: Error: Using TextButton instead of NiaTextButton [DesignSystem] + TextButton() + ~~~~~~~~~~~~ + src/test.kt:9: Error: Using FilterChip instead of NiaFilterChip [DesignSystem] + FilterChip() + ~~~~~~~~~~~~ + src/test.kt:10: Error: Using ElevatedFilterChip instead of NiaFilterChip [DesignSystem] + ElevatedFilterChip() + ~~~~~~~~~~~~~~~~~~~~ + src/test.kt:11: Error: Using NavigationBar instead of NiaNavigationBar [DesignSystem] + NavigationBar() + ~~~~~~~~~~~~~~~ + src/test.kt:12: Error: Using NavigationBarItem instead of NiaNavigationBarItem [DesignSystem] + NavigationBarItem() + ~~~~~~~~~~~~~~~~~~~ + src/test.kt:13: Error: Using NavigationRail instead of NiaNavigationRail [DesignSystem] + NavigationRail() + ~~~~~~~~~~~~~~~~ + src/test.kt:14: Error: Using NavigationRailItem instead of NiaNavigationRailItem [DesignSystem] + NavigationRailItem() + ~~~~~~~~~~~~~~~~~~~~ + src/test.kt:15: Error: Using TabRow instead of NiaTabRow [DesignSystem] + TabRow() + ~~~~~~~~ + src/test.kt:16: Error: Using Tab instead of NiaTab [DesignSystem] + Tab() + ~~~~~ + src/test.kt:17: Error: Using IconToggleButton instead of NiaIconToggleButton [DesignSystem] + IconToggleButton() + ~~~~~~~~~~~~~~~~~~ + src/test.kt:18: Error: Using FilledIconToggleButton instead of NiaIconToggleButton [DesignSystem] + FilledIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:19: Error: Using FilledTonalIconToggleButton instead of NiaIconToggleButton [DesignSystem] + FilledTonalIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:20: Error: Using OutlinedIconToggleButton instead of NiaIconToggleButton [DesignSystem] + OutlinedIconToggleButton() + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:21: Error: Using CenterAlignedTopAppBar instead of NiaTopAppBar [DesignSystem] + CenterAlignedTopAppBar() + ~~~~~~~~~~~~~~~~~~~~~~~~ + src/test.kt:22: Error: Using SmallTopAppBar instead of NiaTopAppBar [DesignSystem] + SmallTopAppBar() + ~~~~~~~~~~~~~~~~ + src/test.kt:23: Error: Using MediumTopAppBar instead of NiaTopAppBar [DesignSystem] + MediumTopAppBar() + ~~~~~~~~~~~~~~~~~ + src/test.kt:24: Error: Using LargeTopAppBar instead of NiaTopAppBar [DesignSystem] + LargeTopAppBar() + ~~~~~~~~~~~~~~~~ + 20 errors, 0 warnings + """.trimIndent(), + ) + } + + @Test + fun `detect replacements of Receiver`() { + lint() + .issues(ISSUE) + .allowMissingSdk() + .files( + COMPOSABLE_STUB, + STUBS, + kotlin( + """ + |fun main() { + ${RECEIVER_NAMES.keys.joinToString("\n") { "| $it.toString()" }} + |} + """.trimMargin(), + ).indented(), + ) + .run() + .expect( + """ + src/test.kt:2: Error: Using Icons instead of NiaIcons [DesignSystem] + Icons.toString() + ~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.trimIndent(), + ) + } + + private companion object { + + private val COMPOSABLE_STUB: TestFile = kotlin( + """ + package androidx.compose.runtime + annotation class Composable + """.trimIndent(), + ).indented() + + private val STUBS: TestFile = kotlin( + """ + |import androidx.compose.runtime.Composable + | + ${METHOD_NAMES.keys.joinToString("\n") { "|@Composable fun $it() = {}" }} + ${RECEIVER_NAMES.keys.joinToString("\n") { "|object $it" }} + | + """.trimMargin(), + ).indented() + } +} From b043d8db8691e34392d1e3e4a8dcd32bed7f68e7 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 22 Dec 2023 11:32:57 +0100 Subject: [PATCH 63/71] Replace workaround on SettingsViewModel now that a fix has been deployed The issue has been addressed in `androidx.compose.ui:ui:1.5.0`. --- .../feature/settings/SettingsViewModel.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt index a49c0d512..123c84d1c 100644 --- a/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/settings/SettingsViewModel.kt @@ -24,12 +24,13 @@ import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Loading import com.google.samples.apps.nowinandroid.feature.settings.SettingsUiState.Success import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds @HiltViewModel class SettingsViewModel @Inject constructor( @@ -48,13 +49,7 @@ class SettingsViewModel @Inject constructor( } .stateIn( scope = viewModelScope, - // Starting eagerly means the user data is ready when the SettingsDialog is laid out - // for the first time. Without this, due to b/221643630 the layout is done using the - // "Loading" text, then replaced with the user editable fields once loaded, however, - // the layout height doesn't change meaning all the fields are squashed into a small - // scrollable column. - // TODO: Change to SharingStarted.WhileSubscribed(5_000) when b/221643630 is fixed - started = SharingStarted.Eagerly, + started = WhileSubscribed(5.seconds.inWholeMilliseconds), initialValue = Loading, ) From 59143b0b79608dc316d6cc130caddc7f7b631346 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 22 Dec 2023 12:08:40 +0100 Subject: [PATCH 64/71] Suppress `LintImplTrimIndent` for `trimMargin()` --- .../nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt b/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt index 6ea55c7e9..188a52ee0 100644 --- a/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt +++ b/lint/src/test/kotlin/com/google/samples/apps/nowinandroid/lint/designsystem/DesignSystemDetectorTest.kt @@ -34,6 +34,7 @@ class DesignSystemDetectorTest { .files( COMPOSABLE_STUB, STUBS, + @Suppress("LintImplTrimIndent") kotlin( """ |import androidx.compose.runtime.Composable @@ -121,6 +122,7 @@ class DesignSystemDetectorTest { .files( COMPOSABLE_STUB, STUBS, + @Suppress("LintImplTrimIndent") kotlin( """ |fun main() { From 046831df36fb59559dbf159368a604d0324c5c0e Mon Sep 17 00:00:00 2001 From: Don Turner Date: Fri, 22 Dec 2023 11:33:22 +0000 Subject: [PATCH 65/71] Update dependency guard classpaths Change-Id: I5f729bce92ec1cc9f71e34e42cbe38765966fc31 --- .../dependencies/releaseRuntimeClasspath.txt | 73 ++++++++++--------- .../prodReleaseRuntimeClasspath.txt | 45 ++++++------ 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt index 7db29f30c..10f2dfa32 100644 --- a/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt +++ b/app-nia-catalog/dependencies/releaseRuntimeClasspath.txt @@ -1,15 +1,16 @@ androidx.activity:activity-compose:1.8.0 androidx.activity:activity-ktx:1.8.0 androidx.activity:activity:1.8.0 -androidx.annotation:annotation-experimental:1.3.0 -androidx.annotation:annotation-jvm:1.6.0 -androidx.annotation:annotation:1.6.0 +androidx.annotation:annotation-experimental:1.3.1 +androidx.annotation:annotation-jvm:1.7.0 +androidx.annotation:annotation:1.7.0 androidx.appcompat:appcompat-resources:1.6.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 androidx.browser:browser:1.6.0 -androidx.collection:collection:1.2.0 +androidx.collection:collection-jvm:1.3.0 +androidx.collection:collection:1.3.0 androidx.compose.animation:animation-android:1.5.4 androidx.compose.animation:animation-core-android:1.5.4 androidx.compose.animation:animation-core:1.5.4 @@ -45,24 +46,24 @@ androidx.compose.ui:ui-util:1.5.4 androidx.compose.ui:ui:1.5.4 androidx.compose:compose-bom:2023.10.01 androidx.concurrent:concurrent-futures:1.1.0 -androidx.core:core-ktx:1.10.0 -androidx.core:core:1.10.0 +androidx.core:core-ktx:1.12.0 +androidx.core:core:1.12.0 androidx.customview:customview-poolingcontainer:1.0.0 androidx.customview:customview:1.0.0 androidx.emoji2:emoji2:1.4.0 androidx.exifinterface:exifinterface:1.3.6 androidx.fragment:fragment:1.5.1 androidx.interpolator:interpolator:1.0.0 -androidx.lifecycle:lifecycle-common-java8:2.6.1 -androidx.lifecycle:lifecycle-common:2.6.1 -androidx.lifecycle:lifecycle-livedata-core:2.6.1 -androidx.lifecycle:lifecycle-livedata:2.6.1 -androidx.lifecycle:lifecycle-process:2.6.1 -androidx.lifecycle:lifecycle-runtime-ktx:2.6.1 -androidx.lifecycle:lifecycle-runtime:2.6.1 -androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1 -androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1 -androidx.lifecycle:lifecycle-viewmodel:2.6.1 +androidx.lifecycle:lifecycle-common-java8:2.6.2 +androidx.lifecycle:lifecycle-common:2.6.2 +androidx.lifecycle:lifecycle-livedata-core:2.6.2 +androidx.lifecycle:lifecycle-livedata:2.6.2 +androidx.lifecycle:lifecycle-process:2.6.2 +androidx.lifecycle:lifecycle-runtime-ktx:2.6.2 +androidx.lifecycle:lifecycle-runtime:2.6.2 +androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2 +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2 +androidx.lifecycle:lifecycle-viewmodel:2.6.2 androidx.loader:loader:1.0.0 androidx.metrics:metrics-performance:1.0.0-alpha04 androidx.profileinstaller:profileinstaller:1.3.1 @@ -74,29 +75,29 @@ 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 -com.google.accompanist:accompanist-drawablepainter:0.30.1 +com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.code.findbugs:jsr305:3.0.2 -com.google.dagger:dagger-lint-aar:2.48.1 -com.google.dagger:dagger:2.48.1 -com.google.dagger:hilt-android:2.48.1 -com.google.dagger:hilt-core:2.48.1 +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.guava:listenablefuture:1.0 -com.squareup.okhttp3:okhttp:4.11.0 -com.squareup.okio:okio-jvm:3.3.0 -com.squareup.okio:okio:3.3.0 -io.coil-kt:coil-base:2.4.0 -io.coil-kt:coil-compose-base:2.4.0 -io.coil-kt:coil-compose:2.4.0 -io.coil-kt:coil:2.4.0 +com.squareup.okhttp3:okhttp:4.12.0 +com.squareup.okio:okio-jvm:3.6.0 +com.squareup.okio:okio:3.6.0 +io.coil-kt:coil-base:2.5.0 +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.10 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 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.10 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1 -org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.1 -org.jetbrains.kotlinx:kotlinx-datetime:0.4.1 +org.jetbrains.kotlin:kotlin-stdlib:1.9.21 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0 +org.jetbrains.kotlinx:kotlinx-datetime:0.5.0 org.jetbrains:annotations:23.0.0 diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 878a5c462..3f88c60ec 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -1,17 +1,18 @@ androidx.activity:activity-compose:1.8.0 androidx.activity:activity-ktx:1.8.0 androidx.activity:activity:1.8.0 -androidx.annotation:annotation-experimental:1.3.0 -androidx.annotation:annotation-jvm:1.6.0 -androidx.annotation:annotation:1.6.0 +androidx.annotation:annotation-experimental:1.3.1 +androidx.annotation:annotation-jvm:1.7.0 +androidx.annotation:annotation:1.7.0 androidx.appcompat:appcompat-resources:1.6.1 androidx.appcompat:appcompat:1.6.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 androidx.autofill:autofill:1.0.0 androidx.browser:browser:1.6.0 -androidx.collection:collection-ktx:1.1.0 -androidx.collection:collection:1.2.0 +androidx.collection:collection-jvm:1.3.0 +androidx.collection:collection-ktx:1.3.0 +androidx.collection:collection:1.3.0 androidx.compose.animation:animation-android:1.5.4 androidx.compose.animation:animation-core-android:1.5.4 androidx.compose.animation:animation-core:1.5.4 @@ -96,9 +97,9 @@ androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 androidx.profileinstaller:profileinstaller:1.3.1 androidx.resourceinspection:resourceinspection-annotation:1.0.1 -androidx.room:room-common:2.6.0 -androidx.room:room-ktx:2.6.0 -androidx.room:room-runtime:2.6.0 +androidx.room:room-common:2.6.1 +androidx.room:room-ktx:2.6.1 +androidx.room:room-runtime:2.6.1 androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate:1.2.1 androidx.sqlite:sqlite-framework:2.4.0 @@ -114,7 +115,7 @@ androidx.window:window:1.0.0 androidx.work:work-runtime-ktx:2.9.0-rc01 androidx.work:work-runtime:2.9.0-rc01 com.caverock:androidsvg-aar:1.4 -com.google.accompanist:accompanist-drawablepainter:0.30.1 +com.google.accompanist:accompanist-drawablepainter:0.32.0 com.google.accompanist:accompanist-permissions:0.32.0 com.google.android.datatransport:transport-api:3.0.0 com.google.android.datatransport:transport-backend-cct:3.1.9 @@ -133,10 +134,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.48.1 -com.google.dagger:dagger:2.48.1 -com.google.dagger:hilt-android:2.48.1 -com.google.dagger:hilt-core:2.48.1 +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.errorprone:error_prone_annotations:2.11.0 com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-analytics-ktx:21.4.0 @@ -175,27 +176,27 @@ com.squareup.okhttp3:okhttp:4.12.0 com.squareup.okio:okio-jvm:3.6.0 com.squareup.okio:okio:3.6.0 com.squareup.retrofit2:retrofit:2.9.0 -io.coil-kt:coil-base:2.4.0 -io.coil-kt:coil-compose-base:2.4.0 -io.coil-kt:coil-compose:2.4.0 -io.coil-kt:coil-svg:2.4.0 -io.coil-kt:coil:2.4.0 +io.coil-kt:coil-base:2.5.0 +io.coil-kt:coil-compose-base:2.5.0 +io.coil-kt:coil-compose:2.5.0 +io.coil-kt:coil-svg:2.5.0 +io.coil-kt:coil:2.5.0 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.10 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21 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.10 +org.jetbrains.kotlin:kotlin-stdlib:1.9.21 org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.7.3 org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3 -org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.1 -org.jetbrains.kotlinx:kotlinx-datetime:0.4.1 +org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0 +org.jetbrains.kotlinx:kotlinx-datetime:0.5.0 org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0 org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.0 org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0 From 61f80739ef131429d4de19d0cf55e8306cea194b Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 22 Dec 2023 12:47:24 +0100 Subject: [PATCH 66/71] Merge `lint` dependency version with `androidTools` --- gradle/libs.versions.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02aa9280f..34c19dd47 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ accompanist = "0.32.0" androidDesugarJdkLibs = "2.0.4" # AGP and tools should be updated together androidGradlePlugin = "8.2.0" -androidTools = "31.1.3" +androidTools = "31.2.0" androidxActivity = "1.8.0" androidxAppCompat = "1.6.1" androidxBrowser = "1.6.0" @@ -47,7 +47,6 @@ kotlinxCoroutines = "1.7.3" kotlinxDatetime = "0.5.0" kotlinxSerializationJson = "1.6.0" ksp = "1.9.21-1.0.16" -lint = "31.2.0" okhttp = "4.12.0" protobuf = "3.24.4" protobufPlugin = "0.9.4" @@ -127,9 +126,9 @@ kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-co kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } -lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "lint" } -lint-checks = { group = "com.android.tools.lint", name = "lint-checks", version.ref = "lint" } -lint-tests = { group = "com.android.tools.lint", name = "lint-tests", version.ref = "lint" } +lint-api = { group = "com.android.tools.lint", name = "lint-api", version.ref = "androidTools" } +lint-checks = { group = "com.android.tools.lint", name = "lint-checks", version.ref = "androidTools" } +lint-tests = { group = "com.android.tools.lint", name = "lint-tests", version.ref = "androidTools" } okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } From 377d3bef0cef20984dfab154bc2ad0c597b7c0b8 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Fri, 2 Jun 2023 23:43:25 +0200 Subject: [PATCH 67/71] Optimize AVD to fix long vector paths Lint warning ... with SVGOM, but you'll have to check on Android Studio the AVD diff. https://googlesamples.github.io/android-custom-lint-rules/checks/VectorPath.md.html --- app/src/main/res/drawable/ic_launcher_foreground.xml | 4 ++-- app/src/main/res/drawable/ic_splash.xml | 6 +++--- .../drawable/core_designsystem_ic_placeholder_default.xml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 6d5711aa5..77280bad5 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -20,10 +20,10 @@ android:viewportWidth="108" android:viewportHeight="108"> diff --git a/app/src/main/res/drawable/ic_splash.xml b/app/src/main/res/drawable/ic_splash.xml index 6de9c8c9b..144393be9 100644 --- a/app/src/main/res/drawable/ic_splash.xml +++ b/app/src/main/res/drawable/ic_splash.xml @@ -24,11 +24,11 @@ android:pathData="M0,0h108v108h-108z" android:fillColor="@color/ic_launcher_background_tint"/> - \ No newline at end of file + diff --git a/core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml b/core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml index a00c2de22..f5d87a103 100644 --- a/core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml +++ b/core/designsystem/src/main/res/drawable/core_designsystem_ic_placeholder_default.xml @@ -32,12 +32,12 @@ android:fillColor="#8C4190" android:fillAlpha="0.11"/> From afdfd8acc52953912e8d74485c65e308a408729d Mon Sep 17 00:00:00 2001 From: SimonMarquis Date: Fri, 22 Dec 2023 15:02:17 +0000 Subject: [PATCH 68/71] =?UTF-8?q?=F0=9F=A4=96=20Updates=20screenshots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Width_compactHeight_showsNavigationBar.png | Bin 37291 -> 37230 bytes ...idth_expandedHeight_showsNavigationBar.png | Bin 107576 -> 107363 bytes ...tWidth_mediumHeight_showsNavigationBar.png | Bin 52265 -> 52205 bytes ...idth_compactHeight_showsNavigationRail.png | Bin 77483 -> 77374 bytes ...dth_expandedHeight_showsNavigationRail.png | Bin 239201 -> 239019 bytes ...Width_mediumHeight_showsNavigationRail.png | Bin 114944 -> 114836 bytes ...idth_compactHeight_showsNavigationRail.png | Bin 59208 -> 59215 bytes ...dth_expandedHeight_showsNavigationRail.png | Bin 155955 -> 156220 bytes ...Width_mediumHeight_showsNavigationRail.png | Bin 84721 -> 84728 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png b/app/src/testDemo/screenshots/compactWidth_compactHeight_showsNavigationBar.png index edb9cfa2af84cd033ba111bc746f1ec24259effc..04ccb84241de8e88a085ef654b0540cc2e536145 100644 GIT binary patch literal 37230 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 z ze&sP?R;j;$ai01rGi|kClSM(^mIdN{UbRZE7aNhLn%%F}78bNzVeBB4m^%h+(D$t$IN-ZoeT&KRIpg&l1D~*^Na6>zBn7KHM5(tRDxR;G_0Vj-R~PZlJcKS<malNf}MTIY2t1Iw5a& zO(+&Z2JvLIC_lD%3-g8Pyh10PP4V8evkw4{TsNyKMVikQ1+7;QH=H0 zm-TJ=PsVIDU7w-Cl&gi)$-|dd#EfM;6L336J(W;<3%)T6wwUp;BAE*wsuEQ5bdzIw zfoksiX-`7hde9|xSua;2gv6-5NUf<=#kMoPvq*To4Uk=Y5kEvFuWIO$&~V6-le{-M zV38J)<36K4Ld)-nA+1W7o@1+K8T7$u%;(UF^lQ!4y+`YXZD!;X)|=bt#_JwmW53g{ zuBIyUR9X^lxlVA!?&8SUu5{j*!rlsyB&PXgEqA3ykf5h?V~&DL^?M zI!IU`(!EnpO0~fDkY^QJS?J@8Ndty{)kRCS<|!%}G;T5+>w`iIxyx~^i>4;^sw-3# zb}wKWG3bXkFwjVLTjpfqn=eii*Bx&=PK=6VWW*90&e2JM$KZw0nsANADx(CyK3<8=nv-mR$Asc1jybbA&5GjZ_xF1620grM` zJ#n_2GkkH}v5{o^v0Tvmvc^#W0ajZI6PQxh?dzEHJQ+|@L#a#TO63+klat{D-JjzG zDX<*-#bvxFIBzdPHRoEj?fh6PJ4OPf(X=AD;o3e{j{A#5-lU}u z;g>sN6GL&4jD&)+--G8}$6FmC5_8W$_;f0vO2H;eS_;GS9X<;N$oU*Llhy>J|$$3c?R@(ru#tD{ldIGS!7%Vz|xlnk2IyziEn*G z6&$CDnzb%Njb#E?$IoOl^n1u7*JZ>q@PA{zLChw2CO<^4Q*n1utp#hp?TmLoZGFU@ z6tx|s>GC778t{{-+-H7#4w0k1Re!n8?a7?4ej;;;nQ{3DoTWw)ekv^%c=?M1u~=>B z2^Ylr#~tg~d5-b}tmpQH#cZ?qXy1~H1cEfHMdI8rS(^7yMqlYdqnse+y6G1JhKgE_ z=glY{!{l*x6CcSZ(X~bH<92f-j^8`?kFGOx$`7*7BejvZ>K(PX>clv00kL{l&mVEg z{nFCJWZ7|>puqiRmB4saNKatNWu0Oe*$xoGw_znc@DEL3P zylbdB`*QXQU*g|5OlyWRE}~g>5a8&<%upu9SZ7WdqHwBXn%iU5xzm0Ym*0~k8a_J_ zG!-_KjHymIz#f0ne7wROJqY=(n(`std)$PtW#JaK(d!|gK2rtt_{8DMxP{p@4RZ<=*c8WRP)Y5 zT@NTi*89f;{&+VGmK_k%vk%&NuNn`-0}h?1OiZ9bZcn(P7FxXP3UtAFmlTqfhZJl3 z=}O+AO)AoSsH(?b%BP&+V+(~pNo3ZmUSgDhJb@W->uMZ_#TnuT>)~$FbAG`GO28g9 z#)eQIj*B}g(7D>IuJF~9_>|jZhF9)Kmhv>xVG>B{__6?^g00}9v7?#)sDwuV*XepQ zkXH6$M5{5M><*wwT&!mWk(=zZDt0npI_uGNrRS9xEJ7}W5;+MSXGQXJl;o2GEuqUz zh-Mm>6Ptxmu{KTPJmW}5*Xg9;w=&rpU@7Nf>yqTx{oXcM{8?pKxc7;2iePtQ6v}Tb z&KUCXUAZ_Cr*xW}AkKzX7%Z;0dSY3*#p0)5eCj3kdmrSt54Yy6`ICylxzC?61Qsla z9^Cy^JUg$kUhdHb+>zA1cUwi)y<3dI-Is{*T$a$fg-DXR% zjUv)lJ+b4UiQh(dZJlZ>z@`OJiMa7qB<0hB9A&A@Nehy+86R*PdGmfq+*7WxRp6Nb<9nlZb2t{Ol9Sz1c z{Pfu|Dz$SIc(*39r^srlO2%fxKBp&gmK$E5x9gX_;Hh!--NR`F@2=mt;kp~EmGmHU zZUEkO*HFt)s}t4idI+dpn81Fm$10I5r{+TXt__FqI{ek)X7cr7k4%T}nNK>m47v@i z%}F?&K1u>DDN5H@4jTG!akc(I)gl=Or~%&Mla7z!v$MII_o@HO5|ta?0p;LHoZEjU zx`k@_ZgXYx=PRrif)!#DZ3V|^dT;Ns^d<>GAnePV7%rbs!f-p}qSCCj$-7d~Nx%E` z5)nQZh3r8PfCT&MPGZmnnYO(tg` zi_qoooI_!=R^GS|l3VX|`XaS@KyO;8bzcQzFIsNj+!7qHgM-v4(Wd5BjzwEVQ9d&x z7INV|<^NJYE@<$;$Uj^GQmP43BM(k-g z8?!#T$lc#MP-!4ZYiLI)G!4=19BWHi^7L2p0cvizijUl=wvT=Ou)rhe;^B4$on93% z=^*sMsr2iZd)p6xsIlw4uzgwD|F~ivS?QHNS$fX6c7eb2z(h+*=-;&#jymSaPNMD0 zuI5Ho(#JG8s1Dh8@rE0P<*T3Kit9NTbih$A;~MRW7r)>B6o>RfBo4A)z|<2HhBW)w ztw}6QMv|!PuTX{~1AJ*i6t%r{MAKW)SD@N+3?zBKk|{rRXLsQ$z0MrNJR$!5+vo-{ z>)jEeBM1GodPSSw_AG~uP@Xeh(Gg%G-Y889z7ig&YRWh=abSvdb~PF!Uzc7kiG`fk zik5!;*AbImS@wJuTWa0!BR_}x2sEK#z>C5R@HtDf@_~v()K?LmzGJ>Izkgo;3TmFF z=_~<6an^#L#@?;nnvTuM_I+BgA{@1))bFLJs>|m`L%jW(URMbzu*?LiCb*PWK?do|YEBThnFQUBdgUpy=fB3TUzi<=*L8rxeO| zC>%Osv)c}@z4aKVUQjo;`rP}MRR9b)5f|_4{}<={nE@R4{NE1CpW!7RJ1sdn-B#s) ziW%(Ee@}9!UHmgM_^WsgsK&I1dbCKw(jB&&fxge%Kj<=w`uP5DbOxC6@SKq8F|=q@ z0*AD(>1%-i(YD=BXXjw^0-vvGfSn4|6aiz2KW=suNcRB!+w`u6X-7d8-$!%0dySt| zV3XbsC|3CT8(o!wk2{P}*2QY!S*^`HEnw`L$=tOzXlHu-cYCzj?H=oerZ6Gz_2>0| zRU&#P@z#T|#}(N4e^=l@SFNOd@hersk#=~oe7@O3Y|%x!t@JUvYR7rnIgpqF?85i& z7-X+MC=g%c`g1!%oAGyo^7-Eh%Dtw4zE51_?*!$;Cx0d=$Bz7;c$LKNbY0l~=up_6 zTQW?VK8Ww8_#uC2FkcGmukV3d_eUO#;EfQDkc`aC;{$|Si>bj=KebsR;$@pB z+K8FWdak35CFE*_ZiVA`@esh=Vy;emqboeUK7mer_?e*H4`!fNg2;-((erj9$IciN zNT1vU@C6(%tN}oyvTA;O)*Yz}RY|4>NwjmEtCLnVT-<0>sGP z_y_rfL%VWuG@_*%>@Q<7lG!OZc{Q#c5t1YTqjq`O?{})yQc*KBj#^)6p)rx)tHPll zRxd|a&^_Lq)$%&CvDeFFulA+Q?)>6_2Cst)u_mcWo@x`NN%y!MOKI?$#v(Nzc3CWJ zBUL~2m*ah>C`CRbB&mr5n!Z-f057n(&xJyrnPHJ>aX&szz z=Am4~Zlm33(ZVJkd%UA+r-lRgH3FleI3_wQx8F!u!I4$)gm^{&`o}`HM6f-r<04`4 zFtKI(Y~y8|nU-~xcBsZieqhQ-w->P`()FlhDv03`r8B3PqR-Yp*TVddwH`r50wrA* zhzYI?ou4G7pKUZ6jDIBpqLGMoN1(!?ZODTP<#9rQXFL~rQyHS2IMXecLbh`(r1SIG zhpu!gkbzwI4%lXNQDAq2Bms})$MH4NAzOLvg^;_s*tHbOFL*rAyOgy`F~B;J-a z#5tPYjO)=!j6REQnl_$C$ft*$<#&k7GVEaDeYk3O4Zbu|CtUi8xI1Uvd=&xd@Ec+V zx^aq);@nLBvRDoF7B8{;8BeA*j1B)!Z97HNyKlg5Lbn9F^=t-fjqQ3BE zwYNf87t6eUY#MA##I221+j8#l&>gCF2YX7c-XYs4H3Ibqc9E(0 z{_TbsS@KLRkR{DY<*ib*Nrx;@+Im$m>&8QLQdpE88iTedv_D)iHgSI~7w?>T3w^SI z?j*XcB3RIYqfd2Dh)&A zivs@{N&TO-ym|z*?Ek?XgM6cA4j%z->th?D^3~@Gj3JZA*kj6Ht$Pl?bBB=eqxf58 zke`7+z|QQ0Jqpu&qYSY4f9~_E#@is~V4^4#JFw9q>4P?v=qFy-k>`Ba6t?kV?y&zo z3HKI_+9SyRD)j$#>Z3f8u}O`0p(=(r*}XioMZ)6)30wibhhuqqdue zR}Yi6Jo|oTNsDM?YYRqio?eHR<-u+oZZ#t!y0^QJ~M!y zzS7dfea%3-eEbS-aC^?b`vs#-9VA1pa(V?&1n8Oi}7RG|F`luq7i-%O63 zB|8ONtEM-{R-DCe^rj)h{PcM|-H%kHJzu{3YHNR}C;GDy6poC+CS%bDr%7lWCJ_m-Um|xn7%! zBA|{m`01~6z1@dzv!GI+@XY`<-rrP%r9QUnxiZ_Z>}Sm-FaNL>ua>Yj9(+5wthf63 zf%>UF>0vQ{hdJh;Uu;wtJCMIZ z`+w93lMpwzHC%uB%KcKg(t6ybI691heM|&|2R~^kI8!aJP#s(_VHFdv^{higbm1gK zjHJ|&W%ww5jAPVz``UV1aWMJ1rQY#~x7eHZ36I^6v;Bn?gPkd&5`k>9_2FdtWZrA- z5U1+sF|}FDl01_4VWQV%U(W`7%b2Lo^T`vw8>(pyJd7>%MRaif@H*CgpipN*H<~G{ zq_7sKSXE-!WV5nYDmcj*9)K2kXn5RPd3ZIRzj3E{xR|}si;)~+$yRk@qTw8FHV{&J zc!0krI#`tR6r*Sf#_Y;-EBN08o}C~n(arUMaGtEt6hYa)zJZuD-3YeXFTP?(01F1N{hT$8+nqS z^RSF8d}qL{y6g6hKA=r{2hIT0c;%qXY$3}JUwqff~%Q5Y^#=qTI-@A*cT@j_AG_0*)-Y+^>A zQr9*2x?%7tN*moFYXU5%)z=wdUzM_cV;T5JcRh-8-O#`0Jv%);ZblNz}9LF z!UlX!$}fmfz#iR84wG_RR8`_ILZLlm50}*R&Q)AJ9S**g24(6if3`U!~3_uyr zs;E#xKyt7=1upFm!ax`pMwF4IAsiVeJ(6!cWlkk}#y(p-e24aIMwzDnP zF=s#A+#5a}#0>O~M*!J~yWRd&JShblC;+W2QQ(LCxpQ7sA{!NwEgs8Rc1qFj(?m#_STMV_KO=v& zj43gYXI;X>T|7yYIIQC|%bMIKo}gB&tmSp-^YQ$&H%m_tsVhQrD*;)PYZ735h4@u4!L522E_Yng(P_+!~(dGqHx7&Yn4x%3%FdTVPMm1)znWwbq?G_`q{D}C=L z^(}UP_yyaCMWRDD#u}s#kM#--U`FS)G95Stb~PKbxyAnN#9lY6VMW&sPk0#IoJjIl zhEw^7&Pue2g`1GE#y>G*9A*La#n%{wBWt%!?W`+GBTdQ{Kd`z&T+?AnB-t!K`9pti z)Lcm9IL0AJRLDmt=06b4z{=zetAWp%G_qK#S(aFy^MB(sc0Zr;6fkB2#rx9Lu_Vjs z?l2{40KNV%Z!%>&ax@pnH>jza9IhL2-F#7@Oe5ti{@wdA$Ws*LyuK`W&p+ zG3@XNSW(}@{0xVr*ESJWr^ctPWys@Q?l%d=zj>UNvCeOvp@%fh;x+gx1Gz_sw2JN9 z^JOW;)*B}a#^(VId-~*A-5_Y3`$2mQS{%*Lu&|i@$`nx*uH;wc@7U@x6txaW8~{7x zT&>@35Wn*x9ToZk+eTu&DC1YipiG(Z&!~=w+!*B^KW9-;yjN?aP*lE{o8qno3Dwg% z*3{Nf@MZW8rAG8}AiHl|p`VJJAdGA%=^6*d$;yoB8yn~G%#GY`17(#zv?mj4G=u4D zZOR%`sa`;FAh8G4#bAjDwM-0Xvk|P_B|qz;6hek7(Vl&U%vqz%s~`V1{jiMFm`2&8 zaxiXD#o8@a$aD5d<-8<;r6-B?Ggrsb8KmSCQmQ1U5j`Gq)k?WppjKST(&I){V|n3D ze|}>esal_~@tCDQ<^wSLuZOyv1gf zgY;oJEhCTYx2G8uY9~yqN~F4y53xJAnI{{4uo$6tRav}$2wn$kyQ-#%103y zQAO7jast5D(X~+$#`jNs(W_OWhvBPAetA1%3M(2I_ak~Q_lT7ov&VW9P1;rvadF?E zKgbq=NOh2-dv`IJX6(N8h~)0u(jw!*f`cj@K++fsdO_g>fKTj0tlX}swm}Q}jxdLH z&frv8|L}t$kI?w@ha+Ntw&Hu)6b~2&WQ4g2w={W60N+pe-&ElWry*m&e8haP_uw4FM-nUznV zTJ#-b1X{TU_@%uswLq2@C*09nJPYWmC%mLtO|SUbK25C6STI{?Pj1rQKir=HqndLe zlA!>99nj@V5fpi3=S(`gjLqtJB>;B(X^z{H3b1re=3UY<5%DM8a!}&t23?&$;P}%V zqhdU?ox^el)ky<4khT8?hox?}$4uEK7gg7;Wk-jXU*RM9=(mSOEahl8-;xdZUkubU zJ+z%YrM9MnlYSaVJAqD>jIYGrsEWxZ6vYg6X1dlu^4--ryy?(M&+!AT5AH|Il-TOx z=@y`!+ANS*BM5wGUOjh1Q5b&Q_r)!1v47##^>lG=nlNh~s7_G&Tu=MJuzcTgzNCjz z3us>+Ozyx@$<*Vw>|RaAcay<&ggH~@v-@&*=kTnq>#Dd0==Q>XiKXsr!SfZC)UFf{ zbyR3^pwcMQn?Q3LS#?T9`ThZTbnlw+IDWvvw;)5<{(S<;c|~0D2!wf3Aj7>yC_zM# z3O%96FId`Qp!G1=O6#&=I9u`vccWmm`li^Q&;2>?^cpuvr@w(I@KM6m<^#`El9Dt1 zCZYJVO@w8{Bt&w6kyaPAB(uip9Yv>`M)4^QHgY8`Hd?voHu_`Uj$ajs6JCal6N8H; zzv*R@^sC0N3UYj*e2ZaRuYA$lL083kR|ba7ZS1~^8tN+0&w;gTU(#@l#LS4UcPb*M zW0R1Z)f&r@)JVP;;T)m z?Fb&QY!5(X;KmN%!^u;Xp2u}n^9ZWpb#mS0v_8J>(&l$fSj3O1Tfgc!_iL!?UZnXn z;wRzrb1gh4B*tYZHjo{`!zvgCxQR`(D>yB~SB2zvj`Z~3DPBhT*O@GB7cma5I>G{k z3)z;XBzBVQwi>qUG-0z2*^(_fE|Xd90s)dZnHo8&4qw=>{|R`uEH{EegcoJb6I7xJ z3CMqlCK-S_eX`Riz^mAP7l8UW@IbK$FPd^yGRFM{2cQmqoAYXDmPIc|#1ne{) z9&M+dA?vx!&G?e0Fb5AW+jDoZjOw?4myGMTSX|R&i~BYF;4t}^v8CgBJ?O4cE?c@0 zoH&BnNhLy~4s_zYM?^i|mqTCb8Pz=|S!B>I_0*Dd?-9YJn6bh-Vxd$3@M@%}f!++u z^WoAfTQ|CL)D7?2`?XVb>xiBL|Kh&=m&34%!oiSk_H5|Y8eaC0$L%|8|9#PEAUylu c7!$v^sq~WXA1xO@y94}ul#mrKdj|^mU%a-Z#Q*>R literal 37291 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