Full Code of android/nowinandroid for AI

main e74c06b73a0e cached
554 files
1.8 MB
463.4k tokens
1 requests
Download .txt
Showing preview only (2,058K chars total). Download the full file or copy to clipboard to get everything.
Repository: android/nowinandroid
Branch: main
Commit: e74c06b73a0e
Files: 554
Total size: 1.8 MB

Directory structure:
gitextract_tu9jpf5p/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── docs_issue.yml
│   │   └── feature_request.yml
│   ├── ci-gradle.properties
│   ├── pull_request_template.md
│   ├── renovate.json
│   └── workflows/
│       ├── Build.yaml
│       ├── NightlyBaselineProfiles.yaml
│       └── Release.yml
├── .gitignore
├── .google/
│   ├── BUILDME
│   └── packaging.yaml
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   └── copyright/
│       ├── The_Android_Open_Source_Project.xml
│       └── profiles_settings.xml
├── .run/
│   ├── Generate Demo Baseline Profile.run.xml
│   └── spotlessApply.run.xml
├── AGENTS.md
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── README.md
│   ├── benchmark-rules.pro
│   ├── build.gradle.kts
│   ├── dependencies/
│   │   └── prodReleaseRuntimeClasspath.txt
│   ├── google-services.json
│   ├── prodRelease-badging.txt
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── kotlin/
│       │       └── com/
│       │           └── google/
│       │               └── samples/
│       │                   └── apps/
│       │                       └── nowinandroid/
│       │                           └── ui/
│       │                               ├── NavigationTest.kt
│       │                               └── UiTestExtensions.kt
│       ├── benchmark/
│       │   └── res/
│       │       ├── values/
│       │       │   └── colors.xml
│       │       └── values-night/
│       │           └── colors.xml
│       ├── debug/
│       │   └── res/
│       │       ├── values/
│       │       │   └── colors.xml
│       │       └── values-night/
│       │           └── colors.xml
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── kotlin/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── samples/
│       │   │               └── apps/
│       │   │                   └── nowinandroid/
│       │   │                       ├── MainActivity.kt
│       │   │                       ├── MainActivityViewModel.kt
│       │   │                       ├── NiaApplication.kt
│       │   │                       ├── di/
│       │   │                       │   └── JankStatsModule.kt
│       │   │                       ├── navigation/
│       │   │                       │   └── TopLevelNavItem.kt
│       │   │                       ├── ui/
│       │   │                       │   ├── NiaApp.kt
│       │   │                       │   └── NiaAppState.kt
│       │   │                       └── util/
│       │   │                           ├── ProfileVerifierLogger.kt
│       │   │                           └── UiExtensions.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── ic_launcher_foreground.xml
│       │       │   └── ic_splash.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── strings.xml
│       │       │   └── themes.xml
│       │       └── values-night/
│       │           ├── colors.xml
│       │           └── themes.xml
│       ├── prod/
│       │   └── AndroidManifest.xml
│       └── testDemo/
│           ├── kotlin/
│           │   └── com/
│           │       └── google/
│           │           └── samples/
│           │               └── apps/
│           │                   └── nowinandroid/
│           │                       └── ui/
│           │                           ├── DeviceConfigurationOverrideWindowInsets.kt
│           │                           ├── NiaAppScreenSizesScreenshotTests.kt
│           │                           ├── NiaAppStateTest.kt
│           │                           ├── SnackbarInsetsScreenshotTests.kt
│           │                           └── SnackbarScreenshotTests.kt
│           └── resources/
│               └── robolectric.properties
├── app-nia-catalog/
│   ├── .gitignore
│   ├── README.md
│   ├── build.gradle.kts
│   ├── dependencies/
│   │   └── releaseRuntimeClasspath.txt
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── kotlin/
│           │   └── com/
│           │       └── google/
│           │           └── samples/
│           │               └── apps/
│           │                   └── niacatalog/
│           │                       ├── NiaCatalogActivity.kt
│           │                       └── ui/
│           │                           └── Catalog.kt
│           └── res/
│               ├── drawable/
│               │   ├── ic_launcher_background.xml
│               │   └── ic_launcher_foreground.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               └── values/
│                   ├── strings.xml
│                   └── themes.xml
├── benchmarks/
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── kotlin/
│               ├── androidx/
│               │   └── test/
│               │       └── uiautomator/
│               │           └── UiAutomatorHelpers.kt
│               └── com/
│                   └── google/
│                       └── samples/
│                           └── apps/
│                               └── nowinandroid/
│                                   ├── BaselineProfileMetrics.kt
│                                   ├── GeneralActions.kt
│                                   ├── Utils.kt
│                                   ├── baselineprofile/
│                                   │   ├── BookmarksBaselineProfile.kt
│                                   │   ├── ForYouBaselineProfile.kt
│                                   │   ├── InterestsBaselineProfile.kt
│                                   │   └── StartupBaselineProfile.kt
│                                   ├── bookmarks/
│                                   │   └── BookmarksActions.kt
│                                   ├── foryou/
│                                   │   ├── ForYouActions.kt
│                                   │   └── ScrollForYouFeedBenchmark.kt
│                                   ├── interests/
│                                   │   ├── InterestsActions.kt
│                                   │   ├── ScrollTopicListBenchmark.kt
│                                   │   ├── ScrollTopicListPowerMetricsBenchmark.kt
│                                   │   └── TopicsScreenRecompositionBenchmark.kt
│                                   └── startup/
│                                       └── StartupBenchmark.kt
├── build-logic/
│   ├── README.md
│   ├── convention/
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               ├── AndroidApplicationComposeConventionPlugin.kt
│   │               ├── AndroidApplicationConventionPlugin.kt
│   │               ├── AndroidApplicationFirebaseConventionPlugin.kt
│   │               ├── AndroidApplicationFlavorsConventionPlugin.kt
│   │               ├── AndroidApplicationJacocoConventionPlugin.kt
│   │               ├── AndroidFeatureApiConventionPlugin.kt
│   │               ├── AndroidFeatureImplConventionPlugin.kt
│   │               ├── AndroidLibraryComposeConventionPlugin.kt
│   │               ├── AndroidLibraryConventionPlugin.kt
│   │               ├── AndroidLibraryJacocoConventionPlugin.kt
│   │               ├── AndroidLintConventionPlugin.kt
│   │               ├── AndroidRoomConventionPlugin.kt
│   │               ├── AndroidTestConventionPlugin.kt
│   │               ├── HiltConventionPlugin.kt
│   │               ├── JvmLibraryConventionPlugin.kt
│   │               ├── RootPlugin.kt
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   ├── AndroidCompose.kt
│   │                                   ├── AndroidInstrumentedTests.kt
│   │                                   ├── Badging.kt
│   │                                   ├── GradleManagedDevices.kt
│   │                                   ├── Graph.kt
│   │                                   ├── Jacoco.kt
│   │                                   ├── KotlinAndroid.kt
│   │                                   ├── NiaBuildType.kt
│   │                                   ├── NiaFlavor.kt
│   │                                   ├── PrintTestApks.kt
│   │                                   ├── ProjectExtensions.kt
│   │                                   └── Spotless.kt
│   ├── gradle.properties
│   └── settings.gradle.kts
├── build.gradle.kts
├── build_android_release.sh
├── compose_compiler_config.conf
├── core/
│   ├── analytics/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── analytics/
│   │       │                                   └── AnalyticsModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── analytics/
│   │       │                                   ├── AnalyticsEvent.kt
│   │       │                                   ├── AnalyticsHelper.kt
│   │       │                                   ├── NoOpAnalyticsHelper.kt
│   │       │                                   ├── StubAnalyticsHelper.kt
│   │       │                                   └── UiHelpers.kt
│   │       └── prod/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── analytics/
│   │                                           ├── AnalyticsModule.kt
│   │                                           └── FirebaseAnalyticsHelper.kt
│   ├── common/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── common/
│   │       │                                   ├── network/
│   │       │                                   │   ├── NiaDispatchers.kt
│   │       │                                   │   └── di/
│   │       │                                   │       ├── CoroutineScopesModule.kt
│   │       │                                   │       └── DispatchersModule.kt
│   │       │                                   └── result/
│   │       │                                       └── Result.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── common/
│   │                                           └── result/
│   │                                               └── ResultKtTest.kt
│   ├── data/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── data/
│   │       │                                   ├── SyncUtilities.kt
│   │       │                                   ├── di/
│   │       │                                   │   ├── DataModule.kt
│   │       │                                   │   └── UserNewsResourceRepositoryModule.kt
│   │       │                                   ├── model/
│   │       │                                   │   ├── NewsResource.kt
│   │       │                                   │   ├── RecentSearchQuery.kt
│   │       │                                   │   └── Topic.kt
│   │       │                                   ├── repository/
│   │       │                                   │   ├── AnalyticsExtensions.kt
│   │       │                                   │   ├── CompositeUserNewsResourceRepository.kt
│   │       │                                   │   ├── DefaultRecentSearchRepository.kt
│   │       │                                   │   ├── DefaultSearchContentsRepository.kt
│   │       │                                   │   ├── NewsRepository.kt
│   │       │                                   │   ├── OfflineFirstNewsRepository.kt
│   │       │                                   │   ├── OfflineFirstTopicsRepository.kt
│   │       │                                   │   ├── OfflineFirstUserDataRepository.kt
│   │       │                                   │   ├── RecentSearchRepository.kt
│   │       │                                   │   ├── SearchContentsRepository.kt
│   │       │                                   │   ├── TopicsRepository.kt
│   │       │                                   │   ├── UserDataRepository.kt
│   │       │                                   │   └── UserNewsResourceRepository.kt
│   │       │                                   └── util/
│   │       │                                       ├── ConnectivityManagerNetworkMonitor.kt
│   │       │                                       ├── NetworkMonitor.kt
│   │       │                                       ├── SyncManager.kt
│   │       │                                       └── TimeZoneMonitor.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       ├── data/
│   │                                       │   ├── CompositeUserNewsResourceRepositoryTest.kt
│   │                                       │   ├── UserNewsResourceTest.kt
│   │                                       │   ├── model/
│   │                                       │   │   └── NetworkEntityTest.kt
│   │                                       │   ├── repository/
│   │                                       │   │   ├── OfflineFirstNewsRepositoryTest.kt
│   │                                       │   │   ├── OfflineFirstTopicsRepositoryTest.kt
│   │                                       │   │   ├── OfflineFirstUserDataRepositoryTest.kt
│   │                                       │   │   └── TestSynchronizer.kt
│   │                                       │   └── testdoubles/
│   │                                       │       ├── TestNewsResourceDao.kt
│   │                                       │       ├── TestNiaNetworkDataSource.kt
│   │                                       │       └── TestTopicDao.kt
│   │                                       └── database/
│   │                                           └── model/
│   │                                               └── PopulatedNewsResourceKtTest.kt
│   ├── data-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── data/
│   │                                           └── test/
│   │                                               ├── AlwaysOnlineNetworkMonitor.kt
│   │                                               ├── DefaultZoneIdTimeZoneMonitor.kt
│   │                                               ├── TestDataModule.kt
│   │                                               └── repository/
│   │                                                   ├── FakeNewsRepository.kt
│   │                                                   ├── FakeRecentSearchRepository.kt
│   │                                                   ├── FakeSearchContentsRepository.kt
│   │                                                   ├── FakeTopicsRepository.kt
│   │                                                   └── FakeUserDataRepository.kt
│   ├── database/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── schemas/
│   │   │   └── com.google.samples.apps.nowinandroid.core.database.NiaDatabase/
│   │   │       ├── 1.json
│   │   │       ├── 10.json
│   │   │       ├── 11.json
│   │   │       ├── 12.json
│   │   │       ├── 13.json
│   │   │       ├── 14.json
│   │   │       ├── 2.json
│   │   │       ├── 3.json
│   │   │       ├── 4.json
│   │   │       ├── 5.json
│   │   │       ├── 6.json
│   │   │       ├── 7.json
│   │   │       ├── 8.json
│   │   │       └── 9.json
│   │   └── src/
│   │       ├── androidTest/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── database/
│   │       │                                   └── dao/
│   │       │                                       ├── DatabaseTest.kt
│   │       │                                       ├── NewsResourceDaoTest.kt
│   │       │                                       └── TopicDaoTest.kt
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── database/
│   │                                           ├── DatabaseMigrations.kt
│   │                                           ├── NiaDatabase.kt
│   │                                           ├── dao/
│   │                                           │   ├── NewsResourceDao.kt
│   │                                           │   ├── NewsResourceFtsDao.kt
│   │                                           │   ├── RecentSearchQueryDao.kt
│   │                                           │   ├── TopicDao.kt
│   │                                           │   └── TopicFtsDao.kt
│   │                                           ├── di/
│   │                                           │   ├── DaosModule.kt
│   │                                           │   └── DatabaseModule.kt
│   │                                           ├── model/
│   │                                           │   ├── NewsResourceEntity.kt
│   │                                           │   ├── NewsResourceFtsEntity.kt
│   │                                           │   ├── NewsResourceTopicCrossRef.kt
│   │                                           │   ├── PopulatedNewsResource.kt
│   │                                           │   ├── RecentSearchQueryEntity.kt
│   │                                           │   ├── TopicEntity.kt
│   │                                           │   └── TopicFtsEntity.kt
│   │                                           └── util/
│   │                                               └── InstantConverter.kt
│   ├── datastore/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── consumer-proguard-rules.pro
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── datastore/
│   │       │                                   ├── ChangeListVersions.kt
│   │       │                                   ├── IntToStringIdsMigration.kt
│   │       │                                   ├── ListToMapMigration.kt
│   │       │                                   ├── NiaPreferencesDataSource.kt
│   │       │                                   ├── UserPreferencesSerializer.kt
│   │       │                                   └── di/
│   │       │                                       └── DataStoreModule.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── datastore/
│   │                                           ├── IntToStringIdsMigrationTest.kt
│   │                                           ├── ListToMapMigrationTest.kt
│   │                                           ├── NiaPreferencesDataSourceTest.kt
│   │                                           └── UserPreferencesSerializerTest.kt
│   ├── datastore-proto/
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── proto/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── data/
│   │                                       ├── dark_theme_config.proto
│   │                                       ├── theme_brand.proto
│   │                                       └── user_preferences.proto
│   ├── datastore-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── datastore/
│   │                                           └── test/
│   │                                               ├── InMemoryDataStore.kt
│   │                                               └── TestDataStoreModule.kt
│   ├── designsystem/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── kotlin/
│   │       │   │   └── com/
│   │       │   │       └── google/
│   │       │   │           └── samples/
│   │       │   │               └── apps/
│   │       │   │                   └── nowinandroid/
│   │       │   │                       └── core/
│   │       │   │                           └── designsystem/
│   │       │   │                               ├── component/
│   │       │   │                               │   ├── Background.kt
│   │       │   │                               │   ├── Button.kt
│   │       │   │                               │   ├── Chip.kt
│   │       │   │                               │   ├── DynamicAsyncImage.kt
│   │       │   │                               │   ├── IconButton.kt
│   │       │   │                               │   ├── LoadingWheel.kt
│   │       │   │                               │   ├── Navigation.kt
│   │       │   │                               │   ├── Tabs.kt
│   │       │   │                               │   ├── Tag.kt
│   │       │   │                               │   ├── TopAppBar.kt
│   │       │   │                               │   ├── ViewToggle.kt
│   │       │   │                               │   └── scrollbar/
│   │       │   │                               │       ├── AppScrollbars.kt
│   │       │   │                               │       ├── LazyScrollbarUtilities.kt
│   │       │   │                               │       ├── Scrollbar.kt
│   │       │   │                               │       ├── ScrollbarExt.kt
│   │       │   │                               │       └── ThumbExt.kt
│   │       │   │                               ├── icon/
│   │       │   │                               │   └── NiaIcons.kt
│   │       │   │                               └── theme/
│   │       │   │                                   ├── Background.kt
│   │       │   │                                   ├── Color.kt
│   │       │   │                                   ├── Gradient.kt
│   │       │   │                                   ├── Theme.kt
│   │       │   │                                   ├── Tint.kt
│   │       │   │                                   └── Type.kt
│   │       │   └── res/
│   │       │       └── drawable/
│   │       │           └── core_designsystem_ic_placeholder_default.xml
│   │       └── test/
│   │           ├── kotlin/
│   │           │   └── com/
│   │           │       └── google/
│   │           │           └── samples/
│   │           │               └── apps/
│   │           │                   └── nowinandroid/
│   │           │                       └── core/
│   │           │                           └── designsystem/
│   │           │                               ├── BackgroundScreenshotTests.kt
│   │           │                               ├── ButtonScreenshotTests.kt
│   │           │                               ├── FilterChipScreenshotTests.kt
│   │           │                               ├── IconButtonScreenshotTests.kt
│   │           │                               ├── LoadingWheelScreenshotTests.kt
│   │           │                               ├── NavigationScreenshotTests.kt
│   │           │                               ├── TabsScreenshotTests.kt
│   │           │                               ├── TagScreenshotTests.kt
│   │           │                               ├── ThemeTest.kt
│   │           │                               └── TopAppBarScreenshotTests.kt
│   │           └── resources/
│   │               └── robolectric.properties
│   ├── domain/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── domain/
│   │       │                                   ├── GetFollowableTopicsUseCase.kt
│   │       │                                   ├── GetRecentSearchQueriesUseCase.kt
│   │       │                                   └── GetSearchContentsUseCase.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── domain/
│   │                                           └── GetFollowableTopicsUseCaseTest.kt
│   ├── model/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── model/
│   │                                           └── data/
│   │                                               ├── DarkThemeConfig.kt
│   │                                               ├── FollowableTopic.kt
│   │                                               ├── NewsResource.kt
│   │                                               ├── SearchResult.kt
│   │                                               ├── ThemeBrand.kt
│   │                                               ├── Topic.kt
│   │                                               ├── UserData.kt
│   │                                               ├── UserNewsResource.kt
│   │                                               └── UserSearchResult.kt
│   ├── navigation/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── navigation/
│   │       │                                   ├── NavigationState.kt
│   │       │                                   └── Navigator.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── navigation/
│   │                                           └── NavigatorTest.kt
│   ├── network/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── lint.xml
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   └── di/
│   │       │                                       └── FlavoredNetworkModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── assets/
│   │       │   │   ├── news.json
│   │       │   │   └── topics.json
│   │       │   └── kotlin/
│   │       │       ├── JvmUnitTestDemoAssetManager.kt
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   ├── NiaNetworkDataSource.kt
│   │       │                                   ├── demo/
│   │       │                                   │   ├── DemoAssetManager.kt
│   │       │                                   │   └── DemoNiaNetworkDataSource.kt
│   │       │                                   ├── di/
│   │       │                                   │   └── NetworkModule.kt
│   │       │                                   ├── model/
│   │       │                                   │   ├── NetworkChangeList.kt
│   │       │                                   │   ├── NetworkNewsResource.kt
│   │       │                                   │   └── NetworkTopic.kt
│   │       │                                   └── retrofit/
│   │       │                                       └── RetrofitNiaNetwork.kt
│   │       ├── prod/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   └── di/
│   │       │                                       └── FlavoredNetworkModule.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── network/
│   │                                           └── demo/
│   │                                               └── DemoNiaNetworkDataSourceTest.kt
│   ├── notifications/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── notifications/
│   │       │                                   └── NotificationsModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── kotlin/
│   │       │   │   └── com/
│   │       │   │       └── google/
│   │       │   │           └── samples/
│   │       │   │               └── apps/
│   │       │   │                   └── nowinandroid/
│   │       │   │                       └── core/
│   │       │   │                           └── notifications/
│   │       │   │                               ├── NoOpNotifier.kt
│   │       │   │                               ├── Notifier.kt
│   │       │   │                               └── SystemTrayNotifier.kt
│   │       │   └── res/
│   │       │       ├── drawable-anydpi-v24/
│   │       │       │   └── core_notifications_ic_nia_notification.xml
│   │       │       └── values/
│   │       │           └── strings.xml
│   │       └── prod/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── notifications/
│   │                                           └── NotificationsModule.kt
│   ├── screenshot-testing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── testing/
│   │                                           └── util/
│   │                                               └── ScreenshotHelper.kt
│   ├── testing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       ├── rules/
│   │                                       │   └── GrantPostNotificationsPermissionRule.kt
│   │                                       └── testing/
│   │                                           ├── NiaTestRunner.kt
│   │                                           ├── data/
│   │                                           │   ├── FollowableTopicTestData.kt
│   │                                           │   ├── NewsResourcesTestData.kt
│   │                                           │   ├── TopicsTestData.kt
│   │                                           │   └── UserNewsResourcesTestData.kt
│   │                                           ├── di/
│   │                                           │   ├── TestDispatcherModule.kt
│   │                                           │   └── TestDispatchersModule.kt
│   │                                           ├── notifications/
│   │                                           │   └── TestNotifier.kt
│   │                                           ├── repository/
│   │                                           │   ├── TestNewsRepository.kt
│   │                                           │   ├── TestRecentSearchRepository.kt
│   │                                           │   ├── TestSearchContentsRepository.kt
│   │                                           │   ├── TestTopicsRepository.kt
│   │                                           │   └── TestUserDataRepository.kt
│   │                                           └── util/
│   │                                               ├── MainDispatcherRule.kt
│   │                                               ├── TestAnalyticsHelper.kt
│   │                                               ├── TestNetworkMonitor.kt
│   │                                               ├── TestSyncManager.kt
│   │                                               └── TestTimeZoneMonitor.kt
│   └── ui/
│       ├── .gitignore
│       ├── README.md
│       ├── build.gradle.kts
│       └── src/
│           ├── androidTest/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── core/
│           │                               └── ui/
│           │                                   └── NewsResourceCardTest.kt
│           └── main/
│               ├── AndroidManifest.xml
│               ├── kotlin/
│               │   └── com/
│               │       └── google/
│               │           └── samples/
│               │               └── apps/
│               │                   └── nowinandroid/
│               │                       └── core/
│               │                           └── ui/
│               │                               ├── AnalyticsExtensions.kt
│               │                               ├── DevicePreviews.kt
│               │                               ├── FollowableTopicPreviewParameterProvider.kt
│               │                               ├── InterestsItem.kt
│               │                               ├── JankStatsExtensions.kt
│               │                               ├── LocalTimeZone.kt
│               │                               ├── NewsFeed.kt
│               │                               ├── NewsResourceCard.kt
│               │                               ├── NewsResourceCardList.kt
│               │                               └── UserNewsResourcePreviewParameterProvider.kt
│               └── res/
│                   └── values/
│                       └── strings.xml
├── docs/
│   ├── ArchitectureLearningJourney.md
│   └── ModularizationLearningJourney.md
├── feature/
│   ├── bookmarks/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── bookmarks/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── BookmarksNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_bookmarks_api_mg_empty_bookmarks.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── bookmarks/
│   │           │                                   └── impl/
│   │           │                                       └── BookmarksScreenTest.kt
│   │           ├── main/
│   │           │   ├── AndroidManifest.xml
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── bookmarks/
│   │           │                                   └── impl/
│   │           │                                       ├── BookmarksScreen.kt
│   │           │                                       ├── BookmarksViewModel.kt
│   │           │                                       └── navigation/
│   │           │                                           └── BookmarksEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── bookmarks/
│   │                                               └── impl/
│   │                                                   └── BookmarksViewModelTest.kt
│   ├── foryou/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── foryou/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── ForYouNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_foryou_api_ic_icon_placeholder.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── foryou/
│   │           │                                   └── impl/
│   │           │                                       └── ForYouScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── foryou/
│   │           │                                   └── impl/
│   │           │                                       ├── ForYouScreen.kt
│   │           │                                       ├── ForYouViewModel.kt
│   │           │                                       ├── OnboardingUiState.kt
│   │           │                                       └── navigation/
│   │           │                                           └── ForYouEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── foryou/
│   │                                               └── impl/
│   │                                                   ├── ForYouScreenScreenshotTests.kt
│   │                                                   └── ForYouViewModelTest.kt
│   ├── interests/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── interests/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── InterestsNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_interests_api_ic_detail_placeholder.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── interests/
│   │           │                                   └── impl/
│   │           │                                       └── InterestsScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── interests/
│   │           │                                   └── impl/
│   │           │                                       ├── InterestsDetailPlaceholder.kt
│   │           │                                       ├── InterestsScreen.kt
│   │           │                                       ├── InterestsViewModel.kt
│   │           │                                       ├── TabContent.kt
│   │           │                                       └── navigation/
│   │           │                                           └── InterestsEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── interests/
│   │                                           └── impl/
│   │                                               ├── InterestsListDetailScreenTest.kt
│   │                                               └── InterestsViewModelTest.kt
│   ├── search/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── search/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── SearchNavKey.kt
│   │   │           └── res/
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── search/
│   │           │                                   └── impl/
│   │           │                                       └── SearchScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── search/
│   │           │                                   └── impl/
│   │           │                                       ├── RecentSearchQueriesUiState.kt
│   │           │                                       ├── SearchResultUiState.kt
│   │           │                                       ├── SearchScreen.kt
│   │           │                                       ├── SearchUiStatePreviewParameterProvider.kt
│   │           │                                       ├── SearchViewModel.kt
│   │           │                                       └── navigation/
│   │           │                                           └── SearchEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── search/
│   │                                               └── impl/
│   │                                                   └── SearchViewModelTest.kt
│   ├── settings/
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── settings/
│   │           │                                   └── impl/
│   │           │                                       └── SettingsDialogTest.kt
│   │           ├── main/
│   │           │   ├── AndroidManifest.xml
│   │           │   ├── kotlin/
│   │           │   │   └── com/
│   │           │   │       └── google/
│   │           │   │           └── samples/
│   │           │   │               └── apps/
│   │           │   │                   └── nowinandroid/
│   │           │   │                       └── feature/
│   │           │   │                           └── settings/
│   │           │   │                               └── impl/
│   │           │   │                                   ├── SettingsDialog.kt
│   │           │   │                                   └── SettingsViewModel.kt
│   │           │   └── res/
│   │           │       └── values/
│   │           │           └── strings.xml
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── settings/
│   │                                               └── impl/
│   │                                                   └── SettingsViewModelTest.kt
│   └── topic/
│       ├── api/
│       │   ├── .gitignore
│       │   ├── README.md
│       │   ├── build.gradle.kts
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           ├── kotlin/
│       │           │   └── com/
│       │           │       └── google/
│       │           │           └── samples/
│       │           │               └── apps/
│       │           │                   └── nowinandroid/
│       │           │                       └── feature/
│       │           │                           └── topic/
│       │           │                               └── api/
│       │           │                                   └── navigation/
│       │           │                                       └── TopicNavKey.kt
│       │           └── res/
│       │               └── values/
│       │                   └── strings.xml
│       └── impl/
│           ├── .gitignore
│           ├── README.md
│           ├── build.gradle.kts
│           └── src/
│               ├── androidTest/
│               │   └── kotlin/
│               │       └── com/
│               │           └── google/
│               │               └── samples/
│               │                   └── apps/
│               │                       └── nowinandroid/
│               │                           └── feature/
│               │                               └── topic/
│               │                                   └── impl/
│               │                                       └── TopicScreenTest.kt
│               ├── main/
│               │   └── kotlin/
│               │       └── com/
│               │           └── google/
│               │               └── samples/
│               │                   └── apps/
│               │                       └── nowinandroid/
│               │                           └── feature/
│               │                               └── topic/
│               │                                   └── impl/
│               │                                       ├── TopicScreen.kt
│               │                                       ├── TopicViewModel.kt
│               │                                       └── navigation/
│               │                                           └── TopicEntryProvider.kt
│               └── test/
│                   └── kotlin/
│                       └── com/
│                           └── google/
│                               └── samples/
│                                   └── apps/
│                                       └── nowinandroid/
│                                           └── feature/
│                                               └── topic/
│                                                   └── impl/
│                                                       └── TopicViewModelTest.kt
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── kokoro/
│   ├── build.sh
│   ├── continuous.cfg
│   ├── nightly.cfg
│   ├── nightly.sh
│   └── presubmit.cfg
├── lint/
│   ├── .gitignore
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── samples/
│       │   │               └── apps/
│       │   │                   └── nowinandroid/
│       │   │                       └── lint/
│       │   │                           ├── NiaIssueRegistry.kt
│       │   │                           ├── TestMethodNameDetector.kt
│       │   │                           └── designsystem/
│       │   │                               └── DesignSystemDetector.kt
│       │   └── resources/
│       │       └── META-INF/
│       │           └── services/
│       │               └── com.android.tools.lint.client.api.IssueRegistry
│       └── test/
│           └── kotlin/
│               └── com/
│                   └── google/
│                       └── samples/
│                           └── apps/
│                               └── nowinandroid/
│                                   └── lint/
│                                       ├── TestMethodNameDetectorTest.kt
│                                       └── designsystem/
│                                           └── DesignSystemDetectorTest.kt
├── settings.gradle.kts
├── spotless/
│   ├── copyright.kt
│   ├── copyright.kts
│   └── copyright.xml
├── sync/
│   ├── sync-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── sync/
│   │                                           └── test/
│   │                                               ├── NeverSyncingSyncManager.kt
│   │                                               └── TestSyncModule.kt
│   └── work/
│       ├── .gitignore
│       ├── README.md
│       ├── build.gradle.kts
│       └── src/
│           ├── androidTest/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── sync/
│           │                               └── workers/
│           │                                   └── SyncWorkerTest.kt
│           ├── demo/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── sync/
│           │                               └── di/
│           │                                   └── SyncModule.kt
│           ├── main/
│           │   ├── kotlin/
│           │   │   └── com/
│           │   │       └── google/
│           │   │           └── samples/
│           │   │               └── apps/
│           │   │                   └── nowinandroid/
│           │   │                       └── sync/
│           │   │                           ├── initializers/
│           │   │                           │   ├── SyncInitializer.kt
│           │   │                           │   └── SyncWorkHelpers.kt
│           │   │                           ├── status/
│           │   │                           │   ├── StubSyncSubscriber.kt
│           │   │                           │   ├── SyncSubscriber.kt
│           │   │                           │   └── WorkManagerSyncManager.kt
│           │   │                           └── workers/
│           │   │                               ├── AnalyticsExtensions.kt
│           │   │                               ├── DelegatingWorker.kt
│           │   │                               └── SyncWorker.kt
│           │   └── res/
│           │       └── values/
│           │           └── strings.xml
│           └── prod/
│               ├── AndroidManifest.xml
│               └── kotlin/
│                   └── com/
│                       └── google/
│                           └── samples/
│                               └── apps/
│                                   └── nowinandroid/
│                                       └── sync/
│                                           ├── di/
│                                           │   └── SyncModule.kt
│                                           ├── services/
│                                           │   └── SyncNotificationsService.kt
│                                           └── status/
│                                               └── FirebaseSyncSubscriber.kt
├── tools/
│   ├── nowinandroid-codestyle.xml
│   ├── pre-push
│   └── setup.sh
└── ui-test-hilt-manifest/
    ├── .gitignore
    ├── README.md
    ├── build.gradle.kts
    └── src/
        └── main/
            ├── AndroidManifest.xml
            └── kotlin/
                └── com/
                    └── google/
                        └── samples/
                            └── apps/
                                └── nowinandroid/
                                    └── uitesthiltmanifest/
                                        └── HiltComponentActivity.kt

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# https://editorconfig.org/
# This configuration is used by ktlint when spotless invokes it

[*.{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, Test
ktlint_standard_backing-property-naming = disabled
ktlint_standard_binary-expression-wrapping = disabled
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_condition-wrapping = disabled
ktlint_standard_function-expression-body = disabled
ktlint_standard_function-literal = disabled
ktlint_standard_function-type-modifier-spacing = disabled
ktlint_standard_multiline-loop = disabled
ktlint_standard_function-signature = disabled


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels: ["bug", "triage me"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: checkboxes
    attributes:
      label: Is there an existing issue for this?
      description: Please search to see if an issue already exists for the bug you encountered.
      options:
      - label: I have searched the existing issues
        required: true
  - type: checkboxes
    attributes:
      label: Is there a StackOverflow question about this issue?
      description: Please search [StackOverflow](https://stackoverflow.com/questions/tagged/android-jetpack) if an issue with an answer already exists for the bug you encountered.
      options:
      - label: I have searched StackOverflow
        required: true
  - type: textarea
    id: what-happened
    attributes:
      label: What happened?
      description: Also tell us, what did you expect to happen?
      placeholder: Tell us what you see!
      value: "A bug happened!"
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Relevant logcat output
      description: Please copy and paste any relevant logcat output. This will be automatically formatted into code, so no need for backticks.
      render: shell
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/docs_issue.yml
================================================
name: Documentation issue
description: File an issue or make a suggestion for the project documentation
title: "[Documentation]: "
labels: ["documentation"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to improve our documentation!
  - type: checkboxes
    attributes:
      label: Is there an existing issue for this?
      description: Please search to see if an issue already exists for the documentation issue you encountered.
      options:
      - label: I have searched the existing issues
        required: true
  - type: input
    id: page-url
    attributes:
      label: Page URL (type "NEW" for a new page suggestion)
    validations:
      required: true
  - type: textarea
    id: what-needs-improving
    attributes:
      label: What's the documentation problem or suggestion?
      placeholder: Tell us what should be improved!
      value: "Docs need improving!"
    validations:
      required: true
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: File a feature request
title: "[FR]: "
labels: ["enhancement", "triage me"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: checkboxes
    attributes:
      label: Is there an existing issue for this?
      description: Please search to see if an issue already exists for this feature request.
      options:
      - label: I have searched the existing issues
        required: true
  - type: textarea
    id: describe-problem
    attributes:
      label: Describe the problem
      description: Is your feature request related to a problem? Please describe.
      placeholder: I'm always frustrated when...
    validations:
      required: true
  - type: textarea
    id: solution
    attributes:
      label: Describe the solution
      description: Please describe the solution you'd like. A clear and concise description of what you want to happen.
    validations:
      required: true
  - type: textarea
    id: context
    attributes:
      label: Additional context
      description: Add any other context or screenshots about the feature request here.
    validations:
      required: false
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true


================================================
FILE: .github/ci-gradle.properties
================================================
#
# Copyright 2020 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
#
#      http://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.
#

org.gradle.daemon=false
org.gradle.parallel=true
org.gradle.workers.max=2
org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true

kotlin.incremental=false

# Controls KotlinOptions.allWarningsAsErrors.
# This value used in CI and is currently set to false.
# If you want to treat warnings as errors locally, set this property to true
# in your ~/.gradle/gradle.properties file.
warningsAsErrors=false


================================================
FILE: .github/pull_request_template.md
================================================
**DO NOT CREATE A PULL REQUEST WITHOUT READING THESE INSTRUCTIONS**

## Instructions
Thanks for submitting a pull request. To accept your pull request we need you do a few things: 

**If this is your first pull request**

- [Sign the contributors license agreement](https://cla.developers.google.com/)

**Ensure tests pass and code is formatted correctly**

- Run local tests on the `DemoDebug` variant by running `./gradlew testDemoDebug`
- Fix code formatting: `./gradlew spotlessApply`

**Add a description**

We need to know what you've done and why you've done it. Include a summary of what your pull request contains, and why you have made these changes. Include links to any relevant issues which it fixes.

[Here's an example](https://github.com/android/nowinandroid/pull/1257).

**NOW DELETE THIS LINE AND EVERYTHING ABOVE IT**

**What I have done and why**

\<add your PR description here\> 


================================================
FILE: .github/renovate.json
================================================
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "local>android/.github:renovate-config"
  ],
  "baseBranches": [
    "main"
  ],
  "gitIgnoredAuthors": [
    "renovate[bot]@users.noreply.github.com",
    "github-actions[bot]@users.noreply.github.com",
    "41898282+github-actions[bot]@users.noreply.github.com"
  ]
}


================================================
FILE: .github/workflows/Build.yaml
================================================
name: Build

on:
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:

concurrency:
  group: build-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test_and_apk:
    name: "Local tests and APKs"
    runs-on: ubuntu-latest

    permissions:
      contents: write
      pull-requests: write
      security-events: write

    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 21
        uses: actions/setup-java@v5
        with:
          distribution: 'zulu'
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4
        with:
          cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
          build-scan-publish: true
          build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
          build-scan-terms-of-use-agree: "yes"

      - name: Check build-logic
        run: ./gradlew :build-logic:convention:check

      - name: Check spotless
        run: ./gradlew spotlessCheck

      - name: Check Dependency Guard
        id: dependencyguard_verify
        continue-on-error: true
        run: ./gradlew dependencyGuard

      - name: Prevent updating Dependency Guard baselines if this is a fork
        id: checkfork_dependencyguard
        continue-on-error: false
        if: steps.dependencyguard_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Dependency Guard failed, please update baselines with: ./gradlew dependencyGuardBaseline" && exit 1

        # Runs if previous job failed
      - name: Generate new Dependency Guard baselines if verification failed and it's a PR
        id: dependencyguard_baseline
        if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
        run: |
          ./gradlew dependencyGuardBaseline

      - name: Push new Dependency Guard baselines if available
        uses: stefanzweifel/git-auto-commit-action@v5
        if: steps.dependencyguard_baseline.outcome == 'success'
        with:
          file_pattern: '**/dependencies/*.txt'
          disable_globbing: true
          commit_message: "🤖 Updates baselines for Dependency Guard"

      - name: Update Graphs
        run: ./gradlew graphUpdate
        continue-on-error: true

      - name: Check Graphs
        id: graphs_verify
        run: git add -- "**/README.md" && git diff --cached --quiet --exit-code -- "**/README.md"

      - name: Prevent updating graphs if this is a fork
        id: checkfork_graphs
        continue-on-error: false
        if: steps.graphs_verify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Check Graphs failed, please update graphs with: ./gradlew graphUpdate" && exit 1

      - name: Push new graphs if available
        if: steps.graphs_verify.outcome == 'failure' && github.event_name == 'pull_request'
        uses: stefanzweifel/git-auto-commit-action@v5
        with:
          file_pattern: '**/README.md'
          disable_globbing: true
          commit_message: "🤖 Updates graphs"

      - name: Run all local screenshot tests (Roborazzi)
        id: screenshotsverify
        continue-on-error: true
        run: ./gradlew verifyRoborazziDemoDebug

      - name: Prevent pushing new screenshots if this is a fork
        id: checkfork_screenshots
        continue-on-error: false
        if: steps.screenshotsverify.outcome == 'failure' && github.event.pull_request.head.repo.full_name != github.repository
        run: |
          echo "::error::Screenshot tests failed, please create a PR in your fork first." 
          echo "Your fork's CI will take screenshots for your fork."
          exit 1

      # Runs if previous job failed
      - name: Generate new screenshots if verification failed and it's a PR
        id: screenshotsrecord
        if: steps.screenshotsverify.outcome == 'failure' && github.event_name == 'pull_request'
        run: |
          ./gradlew recordRoborazziDemoDebug

      - name: Push new screenshots if available
        uses: stefanzweifel/git-auto-commit-action@v5
        if: steps.screenshotsrecord.outcome == 'success'
        with:
          file_pattern: '*/*.png'
          disable_globbing: true
          commit_message: "🤖 Updates screenshots"

      # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots.
      - name: Run local tests
        run: ./gradlew testDemoDebug :lint:test

      - name: Build all build type and flavor permutations
        run: ./gradlew :app:assemble -PminifyWithR8=false

      - name: Upload build outputs (APKs)
        uses: actions/upload-artifact@v4
        with:
          name: APKs
          path: '**/build/outputs/apk/**/*.apk'

      - name: Upload JVM local results (XML)
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: local-test-results
          path: '**/build/test-results/test*UnitTest/**.xml'

      - name: Upload screenshot results (PNG)
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: screenshot-test-results
          path: '**/build/outputs/roborazzi/*_compare.png'

      - name: Check lint
        run: ./gradlew :app:lintProdRelease :app-nia-catalog:lintRelease :lint:lint

      - name: Upload lint reports (HTML)
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: lint-reports
          path: '**/build/reports/lint-results-*.html'

      - name: Upload lint reports (SARIF) for app module
        if: ${{ !cancelled() && hashFiles('app/**/*.sarif') != '' }}
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: './app/'
          category: app

      - name: Upload lint reports (SARIF) for app-nia-catalog module
        if: ${{ !cancelled() && hashFiles('app-nia-catalog/**/*.sarif') != '' }}
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: './app-nia-catalog/'
          category: app-nia-catalog

      - name: Upload lint reports (SARIF) for lint module
        if: ${{ !cancelled() && hashFiles('lint/**/*.sarif') != '' }}
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: './lint/'
          category: lint

      - name: Check badging
        run: ./gradlew :app:checkProdReleaseBadging

  androidTest:
    runs-on: ubuntu-latest
    timeout-minutes: 55
    strategy:
      matrix:
        api-level: [26, 34]

    steps:
      - name: Delete unnecessary tools 🔧
        uses: jlumbroso/free-disk-space@v1.3.1
        with:
          android: false # Don't remove Android tools
          tool-cache: true # Remove image tool cache - rm -rf "$AGENT_TOOLSDIRECTORY"
          dotnet: true # rm -rf /usr/share/dotnet
          haskell: true # rm -rf /opt/ghc...
          swap-storage: true # rm -f /mnt/swapfile (4GiB)
          docker-images: false # Takes 16s, enable if needed in the future
          large-packages: false # includes google-cloud-sdk and it's slow

      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
          ls /dev/kvm

      - name: Checkout
        uses: actions/checkout@v4

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 21
        uses: actions/setup-java@v5
        with:
          distribution: 'zulu'
          java-version: 21

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4
        with:
          cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
          build-scan-publish: true
          build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
          build-scan-terms-of-use-agree: "yes"

      - name: Build projects and run instrumentation tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: ${{ matrix.api-level }}
          arch: x86_64
          disable-animations: true
          disk-size: 6000M
          heap-size: 600M
          script: ./gradlew connectedDemoDebugAndroidTest --daemon

      - name: Run local tests (including Roborazzi) for the combined coverage report (only API 30)
        if: matrix.api-level == 30
        # There is no need to verify Roborazzi tests to generate coverage.
        run: ./gradlew testDemoDebugUnitTest -Proborazzi.test.verify=false # Add Prod if we ever add JVM tests for prod

      # Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod
      - name: Generate coverage reports for Debug variants (only API 30)
        if: matrix.api-level == 30
        run: ./gradlew createDemoDebugCombinedCoverageReport
        
      - name: Upload test reports
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: test-reports-${{ matrix.api-level }}
          path: '**/build/reports/androidTests'

      - name: Display local test coverage (only API 30)
        if: matrix.api-level == 30
        id: jacoco
        uses: madrapps/jacoco-report@v1.7.1
        with:
          title: Combined test coverage report
          min-coverage-overall: 40
          min-coverage-changed-files: 60
          paths: |
            ${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml
          token: ${{ secrets.GITHUB_TOKEN }}
        
      - name: Upload local coverage reports (XML + HTML) (only API 30)
        if: matrix.api-level == 30
        uses: actions/upload-artifact@v4
        with:
          name: coverage-reports
          if-no-files-found: error
          compression-level: 1
          overwrite: false
          path: '**/build/reports/jacoco/'


================================================
FILE: .github/workflows/NightlyBaselineProfiles.yaml
================================================
name: NightlyBaselineProfiles

on:
  workflow_dispatch:
  schedule:
    - cron:  '42 4 * * *'

jobs:
  baseline_profiles:
    name: "Generate Baseline Profiles"
    if: github.repository == 'android/nowinandroid'
    runs-on: ubuntu-latest

    permissions:
      contents: write

    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
          ls /dev/kvm

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 17
        uses: actions/setup-java@v5
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4
        with:
          cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
          build-scan-publish: true
          build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
          build-scan-terms-of-use-agree: "yes"

      - name: Setup Android SDK
        uses: android-actions/setup-android@v3

      - name: Accept licenses
        run: yes | sdkmanager --licenses || true

      - name: Check build-logic
        run: ./gradlew :build-logic:convention:check

      - name: Setup GMD
        run: ./gradlew :benchmarks:pixel6Api33Setup
          --info
          -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
          -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"

      - name: Build all build type and flavor permutations including baseline profiles
        run: ./gradlew :app:assemble
             -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=baselineprofile
             -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
             -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true


================================================
FILE: .github/workflows/Release.yml
================================================
name: GitHub Release with APKs

on:
  workflow_dispatch:
  push:
    tags:
    - 'v*'

jobs:
  build:
    if: github.repository == 'android/nowinandroid'
    runs-on: ubuntu-latest
    timeout-minutes: 120

    steps:
      - name: Enable KVM group perms
        run: |
          echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
          sudo udevadm control --reload-rules
          sudo udevadm trigger --name-match=kvm
          ls /dev/kvm

      - name: Checkout

        uses: actions/checkout@v4

      - name: Copy CI gradle.properties
        run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties

      - name: Set up JDK 17
        uses: actions/setup-java@v5
        with:
          distribution: 'zulu'
          java-version: 17

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4
        with:
          cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
          build-scan-publish: true
          build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
          build-scan-terms-of-use-agree: "yes"

      - name: Setup Android SDK
        uses: android-actions/setup-android@v3

      - name: Accept licenses
        run: yes | sdkmanager --licenses || true

      - name: Setup GMD
        run: ./gradlew :benchmarks:pixel6Api33Setup
          --info
          -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
          -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"

      - name: Build release variant including baseline profile generation
        run: ./gradlew :app:assembleDemoRelease
             -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile
             -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
             -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
             -Pandroid.experimental.androidTest.numManagedDeviceShards=1
             -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: ${{ github.ref }}
          draft: true
          prerelease: false

      - name: Upload app
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: app/build/outputs/apk/demo/release/app-demo-release.apk
          asset_name: app-demo-release.apk
          asset_content_type: application/vnd.android.package-archive


================================================
FILE: .gitignore
================================================
# built application files
*.apk
*.ap_

# files for the dex VM
*.dex

# Java class files
*.class

# generated files
bin/
gen/
out/
build/
generated/

# Local configuration file (sdk path, etc)
local.properties

# Eclipse project files
.classpath
.project

# Windows thumbnail db
.DS_Store

# IDEA/Android Studio project files, because
# the project can be imported from settings.gradle.kts
*.iml
.idea/*
!.idea/copyright
# Keep the code styles.
!/.idea/codeStyles
/.idea/codeStyles/*
!/.idea/codeStyles/Project.xml
!/.idea/codeStyles/codeStyleConfig.xml

# Gradle cache
.gradle

# Sandbox stuff
_sandbox

# Android Studio captures folder
captures/

# Kotlin
.kotlin


================================================
FILE: .google/BUILDME
================================================
# This file can be used to trigger an internal build by changing the number below
2


================================================
FILE: .google/packaging.yaml
================================================
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# GOOGLE SAMPLE PACKAGING DATA
#
# This file is used by Google as part of our samples packaging process.
# End users may safely ignore this file. It has no relevance to other systems.
---
status:       PUBLISHED
technologies: [Android, JetpackCompose, Coroutines]
categories:
  - AndroidTesting
  - AndroidArchitecture
  - AndroidArchitectureUILayer
  - AndroidArchitectureDomainLayer
  - AndroidArchitectureDataLayer
  - AndroidArchitectureStateProduction
  - AndroidArchitectureStateHolder
  - JetpackComposeTesting
  - JetpackComposeA11y
  - JetpackComposeArchitectureAndState
  - JetpackComposeDesignSystems
  - JetpackComposeNavigation
  - JetpackComposeAnimation
solutions:
  - Mobile
  - Flow
  - JetpackHilt
  - JetpackDataStore
  - JetpackRoom
  - JetpackNavigation
  - JetpackWorkManager
  - JetpackLifecycle
languages:    [Kotlin]
github:       android/nowinandroid
level:        ADVANCED
license: apache2


================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <JavaCodeStyleSettings>
      <option name="ANNOTATION_PARAMETER_WRAP" value="1" />
      <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="5" />
      <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="3" />
      <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
        <value>
          <package name="java.awt" withSubpackages="false" static="false" />
          <package name="javax.swing" withSubpackages="false" static="false" />
        </value>
      </option>
      <option name="IMPORT_LAYOUT_TABLE">
        <value>
          <package name="" withSubpackages="true" static="false" />
          <emptyLine />
          <package name="javax" withSubpackages="true" static="false" />
          <package name="java" withSubpackages="true" static="false" />
          <emptyLine />
          <package name="" withSubpackages="true" static="true" />
        </value>
      </option>
      <option name="JD_P_AT_EMPTY_LINES" value="false" />
      <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
      <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
      <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
      <option name="JD_KEEP_EMPTY_RETURN" value="false" />
      <option name="JD_PRESERVE_LINE_FEEDS" value="true" />
    </JavaCodeStyleSettings>
    <JetCodeStyleSettings>
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="99" />
      <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="99" />
      <option name="IMPORT_NESTED_CLASSES" value="true" />
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
    </JetCodeStyleSettings>
    <Properties>
      <option name="KEEP_BLANK_LINES" value="true" />
    </Properties>
    <XML>
      <option name="XML_ATTRIBUTE_WRAP" value="2" />
    </XML>
    <ADDITIONAL_INDENT_OPTIONS fileType="java">
      <option name="TAB_SIZE" value="8" />
    </ADDITIONAL_INDENT_OPTIONS>
    <ADDITIONAL_INDENT_OPTIONS fileType="js">
      <option name="CONTINUATION_INDENT_SIZE" value="4" />
    </ADDITIONAL_INDENT_OPTIONS>
    <codeStyleSettings language="JAVA">
      <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
      <option name="ALIGN_MULTILINE_FOR" value="false" />
      <option name="CALL_PARAMETERS_WRAP" value="1" />
      <option name="PREFER_PARAMETERS_WRAP" value="true" />
      <option name="METHOD_PARAMETERS_WRAP" value="1" />
      <option name="RESOURCE_LIST_WRAP" value="1" />
      <option name="EXTENDS_LIST_WRAP" value="1" />
      <option name="THROWS_LIST_WRAP" value="1" />
      <option name="EXTENDS_KEYWORD_WRAP" value="1" />
      <option name="THROWS_KEYWORD_WRAP" value="1" />
      <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
      <option name="BINARY_OPERATION_WRAP" value="1" />
      <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
      <option name="TERNARY_OPERATION_WRAP" value="1" />
      <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
      <option name="FOR_STATEMENT_WRAP" value="1" />
      <option name="ARRAY_INITIALIZER_WRAP" value="1" />
      <option name="ASSIGNMENT_WRAP" value="1" />
      <option name="IF_BRACE_FORCE" value="3" />
      <option name="DOWHILE_BRACE_FORCE" value="3" />
      <option name="WHILE_BRACE_FORCE" value="3" />
      <option name="FOR_BRACE_FORCE" value="3" />
      <option name="WRAP_LONG_LINES" value="true" />
      <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
      <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
      <option name="ENUM_CONSTANTS_WRAP" value="1" />
    </codeStyleSettings>
    <codeStyleSettings language="JSON">
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
        <option name="TAB_SIZE" value="2" />
      </indentOptions>
    </codeStyleSettings>
    <codeStyleSettings language="XML">
      <option name="FORCE_REARRANGE_MODE" value="1" />
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:layout_width</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:layout_height</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:layout_.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:width</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:height</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:viewportWidth</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:viewportHeight</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:layout_.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:layout_.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
    <codeStyleSettings language="kotlin">
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
      <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
      <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
      <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
      <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
      <option name="FIELD_ANNOTATION_WRAP" value="1" />
      <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
      <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
      <option name="ENUM_CONSTANTS_WRAP" value="5" />
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>

================================================
FILE: .idea/copyright/The_Android_Open_Source_Project.xml
================================================
<component name="CopyrightManager">
  <copyright>
    <option name="notice" value="Copyright &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;    https://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
    <option name="myName" value="The Android Open Source Project" />
  </copyright>
</component>


================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="The Android Open Source Project" />
</component>

================================================
FILE: .run/Generate Demo Baseline Profile.run.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<component name="ProjectRunConfigurationManager">
  <!--
  Baseline Profiles improve code execution speed by around 30% from the first launch by avoiding
  interpretation and just-in-time (JIT) compilation steps for included code paths.
  More information at http://d.android.com/baseline-profiles.

  In this run configuration we leverage rerun parameter that always reruns the requested task regardless of cache.
  We also leverage enable-display parameter to be able to verify the generator works as intended.
  -->
  <configuration default="false" name="Generate Demo Baseline Profile" type="GradleRunConfiguration" factoryName="Gradle">
    <ExternalSystemSettings>
      <option name="executionName" />
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
      <option name="externalSystemIdString" value="GRADLE" />
      <option name="scriptParameters" value="-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile" />
      <option name="taskDescriptions">
        <list />
      </option>
      <option name="taskNames">
        <list>
          <option value=":benchmark:pixel6Api31atdDemoBenchmarkAndroidTest" />
          <option value="--rerun" />
          <option value="--enable-display" />
        </list>
      </option>
      <option name="vmOptions" />
    </ExternalSystemSettings>
    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
    <DebugAllEnabled>false</DebugAllEnabled>
    <method v="2" />
  </configuration>
</component>


================================================
FILE: .run/spotlessApply.run.xml
================================================
<component name="ProjectRunConfigurationManager">
  <configuration default="false" name="spotlessApply" type="GradleRunConfiguration" factoryName="Gradle">
    <ExternalSystemSettings>
      <option name="executionName" />
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
      <option name="externalSystemIdString" value="GRADLE" />
      <option name="taskDescriptions">
        <list />
      </option>
      <option name="taskNames">
        <list>
          <option value="spotlessApply" />
        </list>
      </option>
      <option name="vmOptions" />
    </ExternalSystemSettings>
    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
    <DebugAllEnabled>false</DebugAllEnabled>
    <method v="2" />
  </configuration>
</component>

================================================
FILE: AGENTS.md
================================================
# Now in Android Project

Now in Android is a native Android mobile application written in Kotlin. It provides regular news
about Android development. Users can choose to follow topics, be notified when new content is
available, and bookmark items.

## Architecture

This project is a modern Android application that follows the official architecture guidance from Google. It is a reactive, single-activity app that uses the following:

-   **UI:** Built entirely with Jetpack Compose, including Material 3 components and adaptive layouts for different screen sizes.
-   **State Management:** Unidirectional Data Flow (UDF) is implemented using Kotlin Coroutines and `Flow`s. `ViewModel`s act as state holders, exposing UI state as streams of data.
-   **Dependency Injection:** Hilt is used for dependency injection throughout the app, simplifying the management of dependencies and improving testability.
-   **Navigation:** Navigation is handled by Jetpack Navigation 2 for Compose, allowing for a declarative and type-safe way to navigate between screens.
-   **Data:** The data layer is implemented using the repository pattern.
    -   **Local Data:** Room and DataStore are used for local data persistence.
    -   **Remote Data:** Retrofit and OkHttp are used for fetching data from the network.
-   **Background Processing:** WorkManager is used for deferrable background tasks.

## Modules

The main Android app lives in the `app/` folder. Feature modules live in `feature/` and core and shared modules in `core/`.

## Commands to Build & Test

The app and Android libraries have two product flavors: `demo` and `prod`, and two build types: `debug` and `release`.

- Build: `./gradlew assemble{Variant}`. Typically `assembleDemoDebug`.
- Fix linting/formatting: `./gradlew spotlessApply`
- Run local tests: `./gradlew {variant}Test`
- Run single test: `./gradlew {variant}Test --tests "com.example.myapp.MyTestClass"`
- Run local screenshot tests: `./gradlew verifyRoborazziDemoDebug`

### Instrumented tests

- Gradle-managed devices to run on device tests: `./gradlew pixel6api31aospDebugAndroidTest`. Also `pixel4api30aospatdDebugAndroidTest` and `pixelcapi30aospatdDebugAndroidTest`.

### Creating tests

#### Instrumented tests

- Tests for UI features should only use `ComposeTestRule` with a `ComponentActivity`.
- Bigger tests live in the `:app` module and they can start activities like `MainActivity`.

#### Local tests

- [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) for most assertions
- [cashapp/turbine](https://github.com/cashapp/turbine) for complex coroutine tests
- [google/truth](https://github.com/google/truth) for assertions

## Continuous integration

- The workflows are defined in `.github/workflows/*.yaml` and they contain various checks.
- Screenshot tests are generated by CI, so they shouldn't be checked into the repo from a workstation.

## Version control and code location

- The project uses git and is hosted in https://github.com/android/nowinandroid.


================================================
FILE: CODEOWNERS
================================================
* @dturner


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Google Open Source Community Guidelines

At Google, we recognize and celebrate the creativity and collaboration of open
source contributors and the diversity of skills, experiences, cultures, and
opinions they bring to the projects and communities they participate in.

Every one of Google's open source projects and communities are inclusive
environments, based on treating all individuals respectfully, regardless of
gender identity and expression, sexual orientation, disabilities,
neurodiversity, physical appearance, body size, ethnicity, nationality, race,
age, religion, or similar personal characteristic.

We value diverse opinions, but we value respectful behavior more.

Respectful behavior includes:

* Being considerate, kind, constructive, and helpful.
* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or
  physically threatening behavior, speech, and imagery.
* Not engaging in unwanted physical contact.

Some Google open source projects [may adopt][] an explicit project code of
conduct, which may have additional detailed expectations for participants. Most
of those projects will use our [modified Contributor Covenant][].

[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct
[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/

## Resolve peacefully

We do not believe that all conflict is necessarily bad; healthy debate and
disagreement often yields positive results. However, it is never okay to be
disrespectful.

If you see someone behaving disrespectfully, you are encouraged to address the
behavior directly with those involved. Many issues can be resolved quickly and
easily, and this gives people more control over the outcome of their dispute.
If you are unable to resolve the matter for any reason, or if the behavior is
threatening or harassing, report it. We are dedicated to providing an
environment where participants feel welcome and safe.

## Reporting problems

Some Google open source projects may adopt a project-specific code of conduct.
In those cases, a Google employee will be identified as the Project Steward,
who will receive and handle reports of code of conduct violations. In the event
that a project hasn’t identified a Project Steward, you can report problems by
emailing opensource@google.com.

We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
incidents, which may range from not taking action to permanent expulsion from
the project and project-sponsored spaces. We will notify the accused of the
report and provide them an opportunity to discuss it before any action is
taken. The identity of the reporter will be omitted from the details of the
report supplied to the accused. In potentially harmful situations, such as
ongoing harassment or threats to anyone's safety, we may take action without
notice.

*This document was adapted from the [IndieWeb Code of Conduct][] and can also
be found at <https://opensource.google/conduct/>.*

[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct


================================================
FILE: CONTRIBUTING.md
================================================
# How to become a contributor and submit your own code

## Contributor License Agreements

We'd love to accept your sample apps and patches! Before we can take them, we
have to jump a couple of legal hurdles.

Please fill out either the individual or corporate Contributor License Agreement
(CLA).

  * If you are an individual writing original source code and you're sure you
    own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual).
  * If you work for a company that wants to allow you to contribute your work,
    then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate).

Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able to
accept your pull requests.

## Contributing A Patch

1. Submit an issue describing your proposed change to the repo in question.
1. The repo owner will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a
   Contributor License Agreement (see details above).
1. Fork the desired repo, develop and test your code changes.
1. Ensure that your code adheres to the existing style in the sample to which
   you are contributing. Refer to the
   [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the
   recommended coding standards for this organization.
1. Ensure that your code has an appropriate set of unit tests which all pass.
1. Submit a pull request.



================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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

       http://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.


================================================
FILE: README.md
================================================
![Now in Android](docs/images/nia-splash.jpg "Now in Android")

<a href="https://play.google.com/store/apps/details?id=com.google.samples.apps.nowinandroid"><img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" height="70"></a>

Now in Android App
==================

**Learn how this app was designed and built in the [design case study](https://goo.gle/nia-figma), [architecture learning journey](docs/ArchitectureLearningJourney.md) and [modularization learning journey](docs/ModularizationLearningJourney.md).**

This is the repository for the [Now in Android](https://developer.android.com/series/now-in-android)
app. It is a **work in progress** 🚧.

**Now in Android** is a fully functional Android app built entirely with Kotlin and Jetpack Compose. It
follows Android design and development best practices and is intended to be a useful reference
for developers. As a running app, it's intended to help developers keep up-to-date with the world
of Android development by providing regular news updates.

The app is currently in development. The `prodRelease` variant is [available on the Play Store](https://play.google.com/store/apps/details?id=com.google.samples.apps.nowinandroid).

# Features

**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, and be notified when new content is published which matches interests they are following.

## Screenshots

![Screenshot showing For You screen, Interests screen and Topic detail screen](docs/images/screenshots.png "Screenshot showing For You screen, Interests screen and Topic detail screen")

# Development Environment

**Now in Android** uses the Gradle build system and can be imported directly into Android Studio (make sure you are using the latest stable version available [here](https://developer.android.com/studio)). 

Change the run configuration to `app`.

![image](https://user-images.githubusercontent.com/873212/210559920-ef4a40c5-c8e0-478b-bb00-4879a8cf184a.png)

The `demoDebug` and `demoRelease` build variants can be built and run (the `prod` variants use a backend server which is not currently publicly available).

![image](https://user-images.githubusercontent.com/873212/210560507-44045dc5-b6d5-41ca-9746-f0f7acf22f8e.png)

Once you're up and running, you can refer to the learning journeys below to get a better
understanding of which libraries and tools are being used, the reasoning behind the approaches to
UI, testing, architecture and more, and how all of these different pieces of the project fit
together to create a complete app.

# Architecture

The **Now in Android** app follows the
[official architecture guidance](https://developer.android.com/topic/architecture) 
and is described in detail in the
[architecture learning journey](docs/ArchitectureLearningJourney.md).

# Modularization

The **Now in Android** app has been fully modularized and you can find the detailed guidance and
description of the modularization strategy used in
[modularization learning journey](docs/ModularizationLearningJourney.md).

# Build

The app contains the usual `debug` and `release` build variants. 

In addition, the `benchmark` variant of `app` is used to test startup performance and generate a
baseline profile (see below for more information).

`app-nia-catalog` is a standalone app that displays the list of components that are stylized for
**Now in Android**.

The app also uses
[product flavors](https://developer.android.com/studio/build/build-variants#product-flavors) to
control where content for the app should be loaded from.

The `demo` flavor uses static local data to allow immediate building and exploring of the UI.

The `prod` flavor makes real network calls to a backend server, providing up-to-date content. At 
this time, there is not a public backend available.

For normal development use the `demoDebug` variant. For UI performance testing use the
`demoRelease` variant. 

# Testing

To facilitate testing of components, **Now in Android** uses dependency injection with
[Hilt](https://developer.android.com/training/dependency-injection/hilt-android).

Most data layer components are defined as interfaces.
Then, concrete implementations (with various dependencies) are bound to provide those interfaces to
other components in the app.
In tests, **Now in Android** notably does _not_ use any mocking libraries.
Instead, the production implementations can be replaced with test doubles using Hilt's testing APIs
(or via manual constructor injection for `ViewModel` tests).

These test doubles implement the same interface as the production implementations and generally
provide a simplified (but still realistic) implementation with additional testing hooks.
This results in less brittle tests that may exercise more production code, instead of just verifying
specific calls against mocks.

Examples:
- In instrumentation tests, a temporary folder is used to store the user's preferences, which is
  wiped after each test.
  This allows using the real `DataStore` and exercising all related code, instead of mocking the 
  flow of data updates.

- There are `Test` implementations of each repository, which implement the normal, full repository
  interface and also provide test-only hooks.
  `ViewModel` tests use these `Test` repositories, and thus can use the test-only hooks to
  manipulate the state of the `Test` repository and verify the resulting behavior, instead of
  checking that specific repository methods were called.

To run the tests execute the following gradle tasks: 

- `testDemoDebug` run all local tests against the `demoDebug` variant. Screenshot tests will fail
(see below for explanation). To avoid this, run `recordRoborazziDemoDebug` prior to running unit tests.
- `connectedDemoDebugAndroidTest` run all instrumented tests against the `demoDebug` variant. 

> [!NOTE]
> You should not run `./gradlew test` or `./gradlew connectedAndroidTest` as this will execute 
tests against _all_ build variants which is both unnecessary 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/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt)
to verify that the navigation is displayed correctly on different screen sizes 
([known correct screenshots](https://github.com/android/nowinandroid/tree/main/app/src/testDemo/screenshots)). 

Now In Android uses [Roborazzi](https://github.com/takahirom/roborazzi) to run screenshot tests
of certain screens and UI components. When working with screenshot tests the following gradle tasks are useful:

- `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]
> **Note on failing screenshot tests**   
> 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 
obtain the design files in the [Now in Android Material 3 Case Study](https://goo.gle/nia-figma) (design assets [also available as a PDF](docs/Now-In-Android-Design-File.pdf)).

The Screens and UI elements are built entirely using [Jetpack Compose](https://developer.android.com/jetpack/compose). 

The app has two themes: 

- Dynamic color - uses colors based on the [user's current color theme](https://material.io/blog/announcing-material-you) (if supported)
- Default theme - uses predefined colors when dynamic color is not supported

Each theme also supports dark mode. 

The app uses adaptive layouts to
[support different screen sizes](https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes).

Find out more about the [UI architecture here](docs/ArchitectureLearningJourney.md#ui-layer).

# Performance

## Benchmarks

Find all tests written using [`Macrobenchmark`](https://developer.android.com/topic/performance/benchmarking/macrobenchmark-overview)
in the `benchmarks` module. This module also contains the test to generate the Baseline profile.

## Baseline profiles

The baseline profile for this app is located at [`app/src/main/baseline-prof.txt`](app/src/main/baseline-prof.txt).
It contains rules that enable AOT compilation of the critical user path taken during app launch.
For more information on baseline profiles, read [this document](https://developer.android.com/studio/profile/baselineprofiles).

> [!NOTE]
> The baseline profile needs to be re-generated for release builds that touch code which changes app startup.

To generate the baseline profile, select the `benchmark` build variant and run the
`BaselineProfileGenerator` benchmark test on an AOSP Android Emulator.
Then copy the resulting baseline profile from the emulator to [`app/src/main/baseline-prof.txt`](app/src/main/baseline-prof.txt).

## Compose compiler metrics

Run the following command to get and analyze compose compiler metrics:

```bash
./gradlew assembleRelease -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true
```

The reports files will be added to [build/compose-reports](build/compose-reports). The metrics files will also be 
added to [build/compose-metrics](build/compose-metrics).

For more information on Compose compiler metrics, see [this blog post](https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8).

# License

**Now in Android** is distributed under the terms of the Apache License (Version 2.0). See the
[license](LICENSE) for more information.


================================================
FILE: app/.gitignore
================================================
/build

================================================
FILE: app/README.md
================================================
# `:app`

## Module dependency graph

<!--region graph-->
```mermaid
---
config:
  layout: elk
  elk:
    nodePlacementStrategy: SIMPLE
---
graph TB
  subgraph :feature
    direction TB
    subgraph :feature:settings
      direction TB
      :feature:settings:impl[impl]:::android-library
    end
    subgraph :feature:foryou
      direction TB
      :feature:foryou:api[api]:::android-library
      :feature:foryou:impl[impl]:::android-library
    end
    subgraph :feature:bookmarks
      direction TB
      :feature:bookmarks:api[api]:::android-library
      :feature:bookmarks:impl[impl]:::android-library
    end
    subgraph :feature:search
      direction TB
      :feature:search:api[api]:::android-library
      :feature:search:impl[impl]:::android-library
    end
    subgraph :feature:interests
      direction TB
      :feature:interests:api[api]:::android-library
      :feature:interests:impl[impl]:::android-library
    end
    subgraph :feature:topic
      direction TB
      :feature:topic:api[api]:::android-library
      :feature:topic:impl[impl]:::android-library
    end
  end
  subgraph :sync
    direction TB
    :sync:work[work]:::android-library
  end
  subgraph :core
    direction TB
    :core:analytics[analytics]:::android-library
    :core:common[common]:::jvm-library
    :core:data[data]:::android-library
    :core:database[database]:::android-library
    :core:datastore[datastore]:::android-library
    :core:datastore-proto[datastore-proto]:::jvm-library
    :core:designsystem[designsystem]:::android-library
    :core:domain[domain]:::android-library
    :core:model[model]:::jvm-library
    :core:navigation[navigation]:::android-library
    :core:network[network]:::android-library
    :core:notifications[notifications]:::android-library
    :core:ui[ui]:::android-library
  end
  :benchmarks[benchmarks]:::android-test
  :app[app]:::android-application

  :app -.->|baselineProfile| :benchmarks
  :app -.-> :core:analytics
  :app -.-> :core:common
  :app -.-> :core:data
  :app -.-> :core:designsystem
  :app -.-> :core:model
  :app -.-> :core:ui
  :app -.-> :feature:bookmarks:api
  :app -.-> :feature:bookmarks:impl
  :app -.-> :feature:foryou:api
  :app -.-> :feature:foryou:impl
  :app -.-> :feature:interests:api
  :app -.-> :feature:interests:impl
  :app -.-> :feature:search:api
  :app -.-> :feature:search:impl
  :app -.-> :feature:settings:impl
  :app -.-> :feature:topic:api
  :app -.-> :feature:topic:impl
  :app -.-> :sync:work
  :benchmarks -.->|testedApks| :app
  :core:data -.-> :core:analytics
  :core:data --> :core:common
  :core:data --> :core:database
  :core:data --> :core:datastore
  :core:data --> :core:network
  :core:data -.-> :core:notifications
  :core:database --> :core:model
  :core:datastore -.-> :core:common
  :core:datastore --> :core:datastore-proto
  :core:datastore --> :core:model
  :core:domain --> :core:data
  :core:domain --> :core:model
  :core:network --> :core:common
  :core:network --> :core:model
  :core:notifications -.-> :core:common
  :core:notifications --> :core:model
  :core:ui --> :core:analytics
  :core:ui --> :core:designsystem
  :core:ui --> :core:model
  :feature:bookmarks:api --> :core:navigation
  :feature:bookmarks:impl -.-> :core:data
  :feature:bookmarks:impl -.-> :core:designsystem
  :feature:bookmarks:impl -.-> :core:ui
  :feature:bookmarks:impl -.-> :feature:bookmarks:api
  :feature:bookmarks:impl -.-> :feature:topic:api
  :feature:foryou:api --> :core:navigation
  :feature:foryou:impl -.-> :core:designsystem
  :feature:foryou:impl -.-> :core:domain
  :feature:foryou:impl -.-> :core:notifications
  :feature:foryou:impl -.-> :core:ui
  :feature:foryou:impl -.-> :feature:foryou:api
  :feature:foryou:impl -.-> :feature:topic:api
  :feature:interests:api --> :core:navigation
  :feature:interests:impl -.-> :core:designsystem
  :feature:interests:impl -.-> :core:domain
  :feature:interests:impl -.-> :core:ui
  :feature:interests:impl -.-> :feature:interests:api
  :feature:interests:impl -.-> :feature:topic:api
  :feature:search:api -.-> :core:domain
  :feature:search:api --> :core:navigation
  :feature:search:impl -.-> :core:designsystem
  :feature:search:impl -.-> :core:domain
  :feature:search:impl -.-> :core:ui
  :feature:search:impl -.-> :feature:interests:api
  :feature:search:impl -.-> :feature:search:api
  :feature:search:impl -.-> :feature:topic:api
  :feature:settings:impl -.-> :core:data
  :feature:settings:impl -.-> :core:designsystem
  :feature:settings:impl -.-> :core:ui
  :feature:topic:api -.-> :core:designsystem
  :feature:topic:api --> :core:navigation
  :feature:topic:api -.-> :core:ui
  :feature:topic:impl -.-> :core:data
  :feature:topic:impl -.-> :core:designsystem
  :feature:topic:impl -.-> :core:ui
  :feature:topic:impl -.-> :feature:topic:api
  :sync:work -.-> :core:analytics
  :sync:work -.-> :core:data
  :sync:work -.-> :core:notifications

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```

<details><summary>📋 Graph legend</summary>

```mermaid
graph TB
  application[application]:::android-application
  feature[feature]:::android-feature
  library[library]:::android-library
  jvm[jvm]:::jvm-library

  application -.-> feature
  library --> jvm

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;
```

</details>
<!--endregion-->


================================================
FILE: app/benchmark-rules.pro
================================================
# Proguard rules for the `benchmark` build type.
#
# Obsfuscation must be disabled for the build variant that generates Baseline Profile, otherwise
# wrong symbols would be generated. The generated Baseline Profile will be properly applied when generated
# without obfuscation and your app is being obfuscated.
-dontobfuscate

# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

================================================
FILE: app/build.gradle.kts
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import com.google.samples.apps.nowinandroid.NiaBuildType

plugins {
    alias(libs.plugins.nowinandroid.android.application)
    alias(libs.plugins.nowinandroid.android.application.compose)
    alias(libs.plugins.nowinandroid.android.application.flavors)
    alias(libs.plugins.nowinandroid.android.application.jacoco)
    alias(libs.plugins.nowinandroid.android.application.firebase)
    alias(libs.plugins.nowinandroid.hilt)
    alias(libs.plugins.google.osslicenses)
    alias(libs.plugins.baselineprofile)
    alias(libs.plugins.roborazzi)
    alias(libs.plugins.kotlin.serialization)
}

android {
    defaultConfig {
        applicationId = "com.google.samples.apps.nowinandroid"
        versionCode = 8
        versionName = "0.1.2" // X.Y.Z; X = Major, Y = minor, Z = Patch level

        // Custom test runner to set up Hilt dependency graph
        testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
    }

    buildTypes {
        debug {
            applicationIdSuffix = NiaBuildType.DEBUG.applicationIdSuffix
        }
        release {
            isMinifyEnabled = providers.gradleProperty("minifyWithR8")
                .map(String::toBooleanStrict).getOrElse(true)
            applicationIdSuffix = NiaBuildType.RELEASE.applicationIdSuffix
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),
                          "proguard-rules.pro")

            // To publish on the Play store a private signing key is required, but to allow anyone
            // who clones the code to sign and run the release variant, use the debug signing key.
            // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
            signingConfig = signingConfigs.named("debug").get()
            // Ensure Baseline Profile is fresh for release builds.
            baselineProfile.automaticGenerationDuringBuild = true
        }
    }

    packaging {
        resources {
            excludes.add("/META-INF/{AL2.0,LGPL2.1}")
        }
    }
    testOptions.unitTests.isIncludeAndroidResources = true
    namespace = "com.google.samples.apps.nowinandroid"
}

dependencies {
    implementation(projects.feature.interests.api)
    implementation(projects.feature.interests.impl)
    implementation(projects.feature.foryou.api)
    implementation(projects.feature.foryou.impl)
    implementation(projects.feature.bookmarks.api)
    implementation(projects.feature.bookmarks.impl)
    implementation(projects.feature.topic.api)
    implementation(projects.feature.topic.impl)
    implementation(projects.feature.search.api)
    implementation(projects.feature.search.impl)
    implementation(projects.feature.settings.impl)

    implementation(projects.core.common)
    implementation(projects.core.ui)
    implementation(projects.core.designsystem)
    implementation(projects.core.data)
    implementation(projects.core.model)
    implementation(projects.core.analytics)
    implementation(projects.sync.work)

    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.compose.material3)
    implementation(libs.androidx.navigation3.ui)
    implementation(libs.androidx.compose.material3.adaptive)
    implementation(libs.androidx.compose.material3.adaptive.layout)
    implementation(libs.androidx.compose.material3.adaptive.navigation)
    implementation(libs.androidx.compose.material3.adaptive.navigation3)
    implementation(libs.androidx.compose.material3.windowSizeClass)
    implementation(libs.androidx.compose.runtime.tracing)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.core.splashscreen)
    implementation(libs.androidx.lifecycle.runtimeCompose)
    implementation(libs.androidx.lifecycle.viewModel.navigation3)
    implementation(libs.androidx.profileinstaller)
    implementation(libs.androidx.tracing.ktx)
    implementation(libs.androidx.window.core)
    implementation(libs.kotlinx.coroutines.guava)
    implementation(libs.coil.kt)
    implementation(libs.kotlinx.serialization.json)

    ksp(libs.hilt.compiler)

    debugImplementation(libs.androidx.compose.ui.testManifest)
    debugImplementation(projects.uiTestHiltManifest)

    kspTest(libs.hilt.compiler)

    testImplementation(projects.core.dataTest)
    testImplementation(projects.core.datastoreTest)
    testImplementation(libs.hilt.android.testing)
    testImplementation(projects.sync.syncTest)
    testImplementation(libs.kotlin.test)

    testDemoImplementation(libs.androidx.navigation.testing)
    testDemoImplementation(libs.robolectric)
    testDemoImplementation(libs.roborazzi)
    testDemoImplementation(projects.core.screenshotTesting)
    testDemoImplementation(projects.core.testing)

    androidTestImplementation(projects.core.testing)
    androidTestImplementation(projects.core.dataTest)
    androidTestImplementation(projects.core.datastoreTest)
    androidTestImplementation(libs.androidx.test.espresso.core)
    androidTestImplementation(libs.androidx.compose.ui.test)
    androidTestImplementation(libs.hilt.android.testing)
    androidTestImplementation(libs.kotlin.test)

    baselineProfile(projects.benchmarks)
}

baselineProfile {
    // Don't build on every iteration of a full assemble.
    // Instead enable generation directly for the release build variant.
    automaticGenerationDuringBuild = false

    // Make use of Dex Layout Optimizations via Startup Profiles
    dexLayoutOptimization = true
}

dependencyGuard {
    configuration("prodReleaseRuntimeClasspath")
}


================================================
FILE: app/dependencies/prodReleaseRuntimeClasspath.txt
================================================
androidx.activity:activity-compose:1.12.0
androidx.activity:activity-ktx:1.12.0
androidx.activity:activity:1.12.0
androidx.annotation:annotation-experimental:1.5.1
androidx.annotation:annotation-jvm:1.9.1
androidx.annotation:annotation:1.9.1
androidx.appcompat:appcompat-resources:1.7.0
androidx.appcompat:appcompat:1.7.0
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.8.0
androidx.collection:collection-jvm:1.5.0
androidx.collection:collection-ktx:1.5.0
androidx.collection:collection:1.5.0
androidx.compose.animation:animation-android:1.10.0-beta02
androidx.compose.animation:animation-core-android:1.10.0-beta02
androidx.compose.animation:animation-core:1.10.0-beta02
androidx.compose.animation:animation:1.10.0-beta02
androidx.compose.foundation:foundation-android:1.10.0-beta02
androidx.compose.foundation:foundation-layout-android:1.10.0-beta02
androidx.compose.foundation:foundation-layout:1.10.0-beta02
androidx.compose.foundation:foundation:1.10.0-beta02
androidx.compose.material3.adaptive:adaptive-android:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-layout-android:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-layout:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-navigation-android:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-navigation3-android:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-navigation3:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive-navigation:1.3.0-alpha04
androidx.compose.material3.adaptive:adaptive:1.3.0-alpha04
androidx.compose.material3:material3-adaptive-navigation-suite-android:1.5.0-alpha04
androidx.compose.material3:material3-adaptive-navigation-suite:1.5.0-alpha04
androidx.compose.material3:material3-android:1.5.0-alpha04
androidx.compose.material3:material3-window-size-class-android:1.5.0-alpha04
androidx.compose.material3:material3-window-size-class:1.5.0-alpha04
androidx.compose.material3:material3:1.5.0-alpha04
androidx.compose.material:material-icons-core-android:1.7.8
androidx.compose.material:material-icons-core:1.7.8
androidx.compose.material:material-icons-extended-android:1.7.8
androidx.compose.material:material-icons-extended:1.7.8
androidx.compose.material:material-ripple-android:1.10.0-alpha04
androidx.compose.material:material-ripple:1.10.0-alpha04
androidx.compose.runtime:runtime-android:1.10.0-beta02
androidx.compose.runtime:runtime-annotation-android:1.10.0-beta02
androidx.compose.runtime:runtime-annotation:1.10.0-beta02
androidx.compose.runtime:runtime-retain-android:1.10.0-beta02
androidx.compose.runtime:runtime-retain:1.10.0-beta02
androidx.compose.runtime:runtime-saveable-android:1.10.0-beta02
androidx.compose.runtime:runtime-saveable:1.10.0-beta02
androidx.compose.runtime:runtime-tracing:1.10.0-beta02
androidx.compose.runtime:runtime:1.10.0-beta02
androidx.compose.ui:ui-android:1.10.0-beta02
androidx.compose.ui:ui-geometry-android:1.10.0-beta02
androidx.compose.ui:ui-geometry:1.10.0-beta02
androidx.compose.ui:ui-graphics-android:1.10.0-beta02
androidx.compose.ui:ui-graphics:1.10.0-beta02
androidx.compose.ui:ui-text-android:1.10.0-beta02
androidx.compose.ui:ui-text:1.10.0-beta02
androidx.compose.ui:ui-tooling-preview-android:1.10.0-beta02
androidx.compose.ui:ui-tooling-preview:1.10.0-beta02
androidx.compose.ui:ui-unit-android:1.10.0-beta02
androidx.compose.ui:ui-unit:1.10.0-beta02
androidx.compose.ui:ui-util-android:1.10.0-beta02
androidx.compose.ui:ui-util:1.10.0-beta02
androidx.compose.ui:ui:1.10.0-beta02
androidx.compose:compose-bom-alpha:2025.09.01
androidx.concurrent:concurrent-futures-ktx:1.1.0
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.16.0
androidx.core:core-splashscreen:1.0.1
androidx.core:core-viewtree:1.0.0
androidx.core:core:1.16.0
androidx.cursoradapter:cursoradapter:1.0.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
androidx.datastore:datastore-android:1.2.0
androidx.datastore:datastore-core-android:1.2.0
androidx.datastore:datastore-core-okio-jvm:1.2.0
androidx.datastore:datastore-core-okio:1.2.0
androidx.datastore:datastore-core:1.2.0
androidx.datastore:datastore-preferences-android:1.2.0
androidx.datastore:datastore-preferences-core-android:1.2.0
androidx.datastore:datastore-preferences-core:1.2.0
androidx.datastore:datastore-preferences-external-protobuf:1.2.0
androidx.datastore:datastore-preferences-proto:1.2.0
androidx.datastore:datastore-preferences:1.2.0
androidx.datastore:datastore:1.2.0
androidx.documentfile:documentfile:1.0.0
androidx.drawerlayout:drawerlayout:1.0.0
androidx.dynamicanimation:dynamicanimation:1.0.0
androidx.emoji2:emoji2-views-helper:1.4.0
androidx.emoji2:emoji2:1.4.0
androidx.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.4
androidx.graphics:graphics-path:1.0.1
androidx.graphics:graphics-shapes-android:1.0.1
androidx.graphics:graphics-shapes:1.0.1
androidx.hilt:hilt-common:1.2.0
androidx.hilt:hilt-lifecycle-viewmodel-compose:1.3.0-alpha02
androidx.hilt:hilt-lifecycle-viewmodel:1.3.0-alpha02
androidx.hilt:hilt-work:1.2.0
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.10.0
androidx.lifecycle:lifecycle-common-jvm:2.10.0
androidx.lifecycle:lifecycle-common:2.10.0
androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0
androidx.lifecycle:lifecycle-livedata-core:2.10.0
androidx.lifecycle:lifecycle-livedata:2.10.0
androidx.lifecycle:lifecycle-process:2.10.0
androidx.lifecycle:lifecycle-runtime-android:2.10.0
androidx.lifecycle:lifecycle-runtime-compose-android:2.10.0
androidx.lifecycle:lifecycle-runtime-compose:2.10.0
androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0
androidx.lifecycle:lifecycle-runtime-ktx:2.10.0
androidx.lifecycle:lifecycle-runtime:2.10.0
androidx.lifecycle:lifecycle-service:2.10.0
androidx.lifecycle:lifecycle-viewmodel-android:2.10.0
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.10.0
androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0
androidx.lifecycle:lifecycle-viewmodel-navigation3-android:2.10.0
androidx.lifecycle:lifecycle-viewmodel-navigation3:2.10.0
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0
androidx.lifecycle:lifecycle-viewmodel:2.10.0
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-beta01
androidx.navigation3:navigation3-runtime-android:1.0.0
androidx.navigation3:navigation3-runtime:1.0.0
androidx.navigation3:navigation3-ui-android:1.0.0
androidx.navigation3:navigation3-ui:1.0.0
androidx.navigationevent:navigationevent-android:1.0.0
androidx.navigationevent:navigationevent-compose-android:1.0.0
androidx.navigationevent:navigationevent-compose:1.0.0
androidx.navigationevent:navigationevent:1.0.0
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.4.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.room:room-common-jvm:2.8.3
androidx.room:room-common:2.8.3
androidx.room:room-ktx:2.8.3
androidx.room:room-runtime-android:2.8.3
androidx.room:room-runtime:2.8.3
androidx.savedstate:savedstate-android:1.4.0
androidx.savedstate:savedstate-compose-android:1.4.0
androidx.savedstate:savedstate-compose:1.4.0
androidx.savedstate:savedstate-ktx:1.4.0
androidx.savedstate:savedstate:1.4.0
androidx.sqlite:sqlite-android:2.6.1
androidx.sqlite:sqlite-framework-android:2.6.1
androidx.sqlite:sqlite-framework:2.6.1
androidx.sqlite:sqlite:2.6.1
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.3.0-alpha02
androidx.tracing:tracing-perfetto:1.0.0
androidx.tracing:tracing:1.3.0-alpha02
androidx.transition:transition:1.6.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:window-core-android:1.5.0
androidx.window:window-core:1.5.0
androidx.window:window:1.5.0
androidx.work:work-runtime-ktx:2.10.0
androidx.work:work-runtime:2.10.0
com.caverock:androidsvg-aar:1.4
com.google.accompanist:accompanist-drawablepainter:0.32.0
com.google.accompanist:accompanist-permissions:0.37.0
com.google.android.datatransport:transport-api:3.2.0
com.google.android.datatransport:transport-backend-cct:3.3.0
com.google.android.datatransport:transport-runtime:3.3.0
com.google.android.gms:play-services-ads-identifier:18.0.0
com.google.android.gms:play-services-base:18.5.0
com.google.android.gms:play-services-basement:18.4.0
com.google.android.gms:play-services-cloud-messaging:17.2.0
com.google.android.gms:play-services-measurement-api:22.1.2
com.google.android.gms:play-services-measurement-base:22.1.2
com.google.android.gms:play-services-measurement-impl:22.1.2
com.google.android.gms:play-services-measurement-sdk-api:22.1.2
com.google.android.gms:play-services-measurement-sdk:22.1.2
com.google.android.gms:play-services-measurement:22.1.2
com.google.android.gms:play-services-oss-licenses:17.1.0
com.google.android.gms:play-services-stats:17.0.2
com.google.android.gms:play-services-tasks:18.2.0
com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.59
com.google.dagger:dagger:2.59
com.google.dagger:hilt-android:2.59
com.google.dagger:hilt-core:2.59
com.google.errorprone:error_prone_annotations:2.26.0
com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics:22.1.2
com.google.firebase:firebase-annotations:16.2.0
com.google.firebase:firebase-bom:33.7.0
com.google.firebase:firebase-common-ktx:21.0.0
com.google.firebase:firebase-common:21.0.0
com.google.firebase:firebase-components:18.0.0
com.google.firebase:firebase-config-interop:16.0.1
com.google.firebase:firebase-config:22.0.1
com.google.firebase:firebase-crashlytics:19.3.0
com.google.firebase:firebase-datatransport:19.0.0
com.google.firebase:firebase-encoders-json:18.0.1
com.google.firebase:firebase-encoders-proto:16.0.0
com.google.firebase:firebase-encoders:17.0.0
com.google.firebase:firebase-iid-interop:17.1.0
com.google.firebase:firebase-installations-interop:17.2.0
com.google.firebase:firebase-installations:18.0.0
com.google.firebase:firebase-measurement-connector:20.0.1
com.google.firebase:firebase-messaging:24.1.0
com.google.firebase:firebase-perf:21.0.3
com.google.firebase:firebase-sessions:2.0.7
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-android
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
com.google.protobuf:protobuf-javalite:4.29.2
com.google.protobuf:protobuf-kotlin-lite:4.29.2
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.1
com.squareup.okio:okio:3.9.1
com.squareup.retrofit2:converter-kotlinx-serialization:2.11.0
com.squareup.retrofit2:retrofit:2.11.0
io.coil-kt:coil-base:2.7.0
io.coil-kt:coil-compose-base:2.7.0
io.coil-kt:coil-compose:2.7.0
io.coil-kt:coil-svg:2.7.0
io.coil-kt:coil:2.7.0
jakarta.inject:jakarta.inject-api:2.0.1
javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-stdlib-common:2.3.0
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
org.jetbrains.kotlin:kotlin-stdlib:2.3.0
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.10.1
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.1
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1
org.jetbrains.kotlinx:kotlinx-datetime:0.6.1
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-core:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0
org.jetbrains:annotations:23.0.0
org.jspecify:jspecify:1.0.0


================================================
FILE: app/google-services.json
================================================
{
  "project_info": {
    "project_number": "YourProjectId",
    "project_id": "abc",
    "storage_bucket": "abc"
  },
  "client": [
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    },
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid.demo.debug"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    },
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid.demo.benchmark"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    },
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid.benchmark"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    },
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid.debug"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    },
    {
      "client_info": {
        "mobilesdk_app_id": "Your:App:Id",
        "android_client_info": {
          "package_name": "com.google.samples.apps.nowinandroid.demo"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "APlaceholderAPIKeyWith-ThirtyNineCharsX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    }
  ],

  "configuration_version": "1"
}

================================================
FILE: app/prodRelease-badging.txt
================================================
package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='16' platformBuildVersionCode='36' compileSdkVersion='36' compileSdkVersionCodename='16'
minSdkVersion:'23'
targetSdkVersion:'36'
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='com.google.samples.apps.nowinandroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
application-label:'Now in Android'
application-label-af:'Now in Android'
application-label-am:'Now in Android'
application-label-ar:'Now in Android'
application-label-as:'Now in Android'
application-label-az:'Now in Android'
application-label-be:'Now in Android'
application-label-bg:'Now in Android'
application-label-bn:'Now in Android'
application-label-bs:'Now in Android'
application-label-ca:'Now in Android'
application-label-cs:'Now in Android'
application-label-da:'Now in Android'
application-label-de:'Now in Android'
application-label-el:'Now in Android'
application-label-en-AU:'Now in Android'
application-label-en-CA:'Now in Android'
application-label-en-GB:'Now in Android'
application-label-en-IN:'Now in Android'
application-label-en-XC:'Now in Android'
application-label-es:'Now in Android'
application-label-es-US:'Now in Android'
application-label-et:'Now in Android'
application-label-eu:'Now in Android'
application-label-fa:'Now in Android'
application-label-fi:'Now in Android'
application-label-fr:'Now in Android'
application-label-fr-CA:'Now in Android'
application-label-gl:'Now in Android'
application-label-gu:'Now in Android'
application-label-hi:'Now in Android'
application-label-hr:'Now in Android'
application-label-hu:'Now in Android'
application-label-hy:'Now in Android'
application-label-in:'Now in Android'
application-label-is:'Now in Android'
application-label-it:'Now in Android'
application-label-iw:'Now in Android'
application-label-ja:'Now in Android'
application-label-ka:'Now in Android'
application-label-kk:'Now in Android'
application-label-km:'Now in Android'
application-label-kn:'Now in Android'
application-label-ko:'Now in Android'
application-label-ky:'Now in Android'
application-label-lo:'Now in Android'
application-label-lt:'Now in Android'
application-label-lv:'Now in Android'
application-label-mk:'Now in Android'
application-label-ml:'Now in Android'
application-label-mn:'Now in Android'
application-label-mr:'Now in Android'
application-label-ms:'Now in Android'
application-label-my:'Now in Android'
application-label-nb:'Now in Android'
application-label-ne:'Now in Android'
application-label-nl:'Now in Android'
application-label-or:'Now in Android'
application-label-pa:'Now in Android'
application-label-pl:'Now in Android'
application-label-pt:'Now in Android'
application-label-pt-BR:'Now in Android'
application-label-pt-PT:'Now in Android'
application-label-ro:'Now in Android'
application-label-ru:'Now in Android'
application-label-si:'Now in Android'
application-label-sk:'Now in Android'
application-label-sl:'Now in Android'
application-label-sq:'Now in Android'
application-label-sr:'Now in Android'
application-label-sr-Latn:'Now in Android'
application-label-sv:'Now in Android'
application-label-sw:'Now in Android'
application-label-ta:'Now in Android'
application-label-te:'Now in Android'
application-label-th:'Now in Android'
application-label-tl:'Now in Android'
application-label-tr:'Now in Android'
application-label-uk:'Now in Android'
application-label-ur:'Now in Android'
application-label-uz:'Now in Android'
application-label-vi:'Now in Android'
application-label-zh-CN:'Now in Android'
application-label-zh-HK:'Now in Android'
application-label-zh-TW:'Now in Android'
application-label-zu:'Now in Android'
application-icon-120:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-160:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-240:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-320:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-480:'res/mipmap-anydpi-v26/ic_launcher.xml'
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'
feature-group: label=''
  uses-feature: name='android.hardware.faketouch'
  uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
main
other-activities
other-receivers
other-services
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'sk' 'sl' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'uk' 'ur' 'uz' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'


================================================
FILE: app/proguard-rules.pro
================================================
# Repackage classes into the default package to reduce the size of descriptors.
-repackageclasses


================================================
FILE: app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import androidx.compose.ui.semantics.SemanticsActions.ScrollBy
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollToNode
import androidx.test.espresso.Espresso
import androidx.test.espresso.NoActivityResumedException
import com.google.samples.apps.nowinandroid.MainActivity
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.rules.GrantPostNotificationsPermissionRule
import com.google.samples.apps.nowinandroid.feature.interests.impl.LIST_PANE_TEST_TAG
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R as BookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.api.R as FeatureForyouR
import com.google.samples.apps.nowinandroid.feature.search.api.R as FeatureSearchR
import com.google.samples.apps.nowinandroid.feature.settings.impl.R as SettingsR

/**
 * Tests all the navigation flows that are handled by the navigation library.
 */
@HiltAndroidTest
class NavigationTest {

    /**
     * Manages the components' state and is used to perform injection on your test
     */
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    /**
     * Grant [android.Manifest.permission.POST_NOTIFICATIONS] permission.
     */
    @get:Rule(order = 1)
    val postNotificationsPermission = GrantPostNotificationsPermissionRule()

    /**
     * Use the primary activity to initialize the app normally.
     */
    @get:Rule(order = 2)
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Inject
    lateinit var topicsRepository: TopicsRepository

    @Inject
    lateinit var newsRepository: NewsRepository

    // The strings used for matching in these tests
    private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_api_navigate_up)
    private val forYou by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_api_title)
    private val interests by composeTestRule.stringResource(FeatureSearchR.string.feature_search_api_interests)
    private val sampleTopic = "Headlines"
    private val appName by composeTestRule.stringResource(R.string.app_name)
    private val saved by composeTestRule.stringResource(BookmarksR.string.feature_bookmarks_api_title)
    private val settings by composeTestRule.stringResource(SettingsR.string.feature_settings_impl_top_app_bar_action_icon_description)
    private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_impl_brand_android)
    private val ok by composeTestRule.stringResource(SettingsR.string.feature_settings_impl_dismiss_dialog_button_text)

    @Before
    fun setup() = hiltRule.inject()

    @Test
    fun firstScreen_isForYou() {
        composeTestRule.apply {
            // VERIFY for you is selected
            onNodeWithText(forYou).assertIsSelected()
        }
    }

    // TODO: implement tests related to navigation & resetting of destinations (b/213307564)
    // Restoring content should be tested with another tab than the For You one, as that will
    // still succeed even when restoring state is turned off.
    /**
     * When navigating between the different top level destinations, we should restore the state
     * of previously visited destinations.
     */
    @Test
    fun navigationBar_navigateToPreviouslySelectedTab_restoresContent() {
        composeTestRule.apply {
            // GIVEN the user follows a topic
            onNodeWithText(sampleTopic).performClick()
            // WHEN the user navigates to the Interests destination
            onNodeWithText(interests).performClick()
            // AND the user navigates to the For You destination
            onNodeWithText(forYou).performClick()
            // THEN the state of the For You destination is restored
            onNodeWithContentDescription(sampleTopic).assertIsOn()
        }
    }

    /**
     * When reselecting a tab, it should show that tab's start destination and restore its state.
     */
    @Test
    fun navigationBar_reselectTab_keepsState() {
        composeTestRule.apply {
            // GIVEN the user follows a topic
            onNodeWithText(sampleTopic).performClick()
            // WHEN the user taps the For You navigation bar item
            onNodeWithText(forYou).performClick()
            // THEN the state of the For You destination is restored
            onNodeWithContentDescription(sampleTopic).assertIsOn()
        }
    }

//    @Test
//    fun navigationBar_reselectTab_resetsToStartDestination() {
//        // GIVEN the user is on the Topics destination and scrolls
//        // and navigates to the Topic Detail destination
//        // WHEN the user taps the Topics navigation bar item
//        // THEN the Topics destination shows in the same scrolled state
//    }

    /*
     * Top level destinations should never show an up affordance.
     */
    @Test
    fun topLevelDestinations_doNotShowUpArrow() {
        composeTestRule.apply {
            // GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown.
            onNodeWithContentDescription(navigateUp).assertDoesNotExist()

            onNodeWithText(saved).performClick()
            onNodeWithContentDescription(navigateUp).assertDoesNotExist()

            onNodeWithText(interests).performClick()
            onNodeWithContentDescription(navigateUp).assertDoesNotExist()
        }
    }

    @Test
    fun topLevelDestinations_showTopBarWithTitle() {
        composeTestRule.apply {
            // Verify that the top bar contains the app name on the first screen.
            onNodeWithText(appName).assertExists()

            // Go to the saved tab, verify that the top bar contains "saved". This means
            // we'll have 2 elements with the text "saved" on screen. One in the top bar, and
            // one in the bottom navigation.
            onNodeWithText(saved).performClick()
            onAllNodesWithText(saved).assertCountEquals(2)

            // As above but for the interests tab.
            onNodeWithText(interests).performClick()
            onAllNodesWithText(interests).assertCountEquals(2)
        }
    }

    @Test
    fun topLevelDestinations_showSettingsIcon() {
        composeTestRule.apply {
            onNodeWithContentDescription(settings).assertExists()

            onNodeWithText(saved).performClick()
            onNodeWithContentDescription(settings).assertExists()

            onNodeWithText(interests).performClick()
            onNodeWithContentDescription(settings).assertExists()
        }
    }

    @Test
    fun whenSettingsIconIsClicked_settingsDialogIsShown() {
        composeTestRule.apply {
            onNodeWithContentDescription(settings).performClick()

            // Check that one of the settings is actually displayed.
            onNodeWithText(brand).assertExists()
        }
    }

    @Test
    fun whenSettingsDialogDismissed_previousScreenIsDisplayed() {
        composeTestRule.apply {
            // Navigate to the saved screen, open the settings dialog, then close it.
            onNodeWithText(saved).performClick()
            onNodeWithContentDescription(settings).performClick()
            onNodeWithText(ok).performClick()

            // Check that the saved screen is still visible and selected.
            onNode(hasText(saved) and hasTestTag("NiaNavItem")).assertIsSelected()
        }
    }

    /*
     * There should always be at most one instance of a top-level destination at the same time.
     */
    @Test(expected = NoActivityResumedException::class)
    fun homeDestination_back_quitsApp() {
        composeTestRule.apply {
            // GIVEN the user navigates to the Interests destination
            onNodeWithText(interests).performClick()
            // and then navigates to the For you destination
            onNodeWithText(forYou).performClick()
            // WHEN the user uses the system button/gesture to go back
            Espresso.pressBack()
            // THEN the app quits
        }
    }

    /*
     * When pressing back from any top level destination except "For you", the app navigates back
     * to the "For you" destination, no matter which destinations you visited in between.
     */
    @Test
    fun navigationBar_backFromAnyDestination_returnsToForYou() {
        composeTestRule.apply {
            // GIVEN the user navigated to the Interests destination
            onNodeWithText(interests).performClick()
            // TODO: Add another destination here to increase test coverage, see b/226357686.
            // WHEN the user uses the system button/gesture to go back,
            Espresso.pressBack()
            // THEN the app shows the For You destination
            onNodeWithText(forYou).assertExists()
        }
    }

    // TODO decide if backStack should preserve previous stacks when navigating back to home tab (ForYou)
    // https://github.com/android/nowinandroid/issues/1937
    @Ignore
    @Test
    fun navigationBar_multipleBackStackInterests() {
        composeTestRule.apply {
            onNodeWithText(interests).performClick()

            // Select the last topic
            val topic = runBlocking {
                topicsRepository.getTopics().first().sortedBy(Topic::name).last()
            }
            onNodeWithTag(LIST_PANE_TEST_TAG).performScrollToNode(hasText(topic.name))
            onNodeWithText(topic.name).performClick()

            // Verify the topic is still shown
            onNodeWithTag("topic:${topic.id}").assertIsDisplayed()

            // Switch tab
            onNodeWithText(forYou).performClick()
            // Come back to Interests
            onNodeWithText(interests).performClick()

            // Verify the topic is still shown
            onNodeWithTag("topic:${topic.id}").assertExists()
        }
    }

    @Test
    fun navigatingToTopicFromForYou_showsTopicDetails() {
        composeTestRule.apply {
            // Get the first news resource
            val newsResource = runBlocking {
                newsRepository.getNewsResources().first().first()
            }

            // Get its first topic and follow it
            val topic = newsResource.topics.first()
            onNodeWithText(topic.name).performClick()

            // Get the news feed and scroll to the news resource
            // Note: Possible flakiness. If the content of the news resource is long then the topic
            // tag might not be visible meaning it cannot be clicked
            onNodeWithTag("forYou:feed")
                .performScrollToNode(hasTestTag("newsResourceCard:${newsResource.id}"))
                .fetchSemanticsNode()
                .apply {
                    val newsResourceCardNode = onNodeWithTag("newsResourceCard:${newsResource.id}")
                        .fetchSemanticsNode()
                    config[ScrollBy].action?.invoke(
                        0f,
                        // to ensure the bottom of the card is visible,
                        // manually scroll the difference between the height of
                        // the scrolling node and the height of the card
                        (newsResourceCardNode.size.height - size.height).coerceAtLeast(0).toFloat(),
                    )
                }

            // Click the first topic tag
            onAllNodesWithTag("topicTag:${topic.id}", useUnmergedTree = true)
                .onFirst()
                .performClick()

            // Verify that we're on the correct topic details screen
            onNodeWithTag("topic:${topic.id}").assertExists()
        }
    }
}


================================================
FILE: app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/UiTestExtensions.kt
================================================
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import androidx.annotation.StringRes
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import kotlin.properties.ReadOnlyProperty

fun AndroidComposeTestRule<*, *>.stringResource(
    @StringRes resId: Int,
): ReadOnlyProperty<Any, String> =
    ReadOnlyProperty { _, _ -> activity.getString(resId) }


================================================
FILE: app/src/benchmark/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     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

          http://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.
-->
<resources>
    <!-- Allow users to distinguish between build variants by having a different background color
        for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
    <color name="ic_launcher_background_tint">#000000</color>
    <color name="ic_launcher_foreground_tint">#FF006780</color>
</resources>


================================================
FILE: app/src/benchmark/res/values-night/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     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

          http://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.
-->
<resources>
    <!-- Allow users to distinguish between build variants by having a different background color
        for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
    <color name="ic_launcher_background_tint">#FFFFFF</color>
    <color name="ic_launcher_foreground_tint">#FF006780</color>
</resources>


================================================
FILE: app/src/debug/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     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

          http://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.
-->
<resources>
    <!-- Allow users to distinguish between build variants by having a different background color
    for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
    <color name="ic_launcher_background_tint">#000000</color>
    <color name="ic_launcher_foreground_tint">#FFA23F16</color>
</resources>


================================================
FILE: app/src/debug/res/values-night/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     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

          http://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.
-->
<resources>
    <!-- Allow users to distinguish between build variants by having a different background color
    for the launcher icon. See https://github.com/android/nowinandroid/pull/989. -->
    <color name="ic_launcher_background_tint">#FFFFFF</color>
    <color name="ic_launcher_foreground_tint">#FFA23F16</color>
</resources>


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <!--
    Firebase automatically adds these AD_ID and ADSERVICES permissions, even though we don't use them.
    If you use these permissions you must declare how you're using them to Google Play, otherwise the
    app will be rejected when publishing it. To avoid this we remove the permissions entirely.
    -->
    <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>
    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" tools:node="remove"/>
    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" tools:node="remove"/>

    <application
        android:name=".NiaApplication"
        android:allowBackup="true"
        android:enableOnBackInvokedCallback="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Nia.Splash">
        <profileable android:shell="true" tools:targetApi="q" />

        <activity
            android:name=".MainActivity"
            android:configChanges="uiMode"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="https" />
                <data android:host="www.nowinandroid.apps.samples.google.com" />
            </intent-filter>
        </activity>

        <!-- Disable Firebase analytics by default. This setting is overwritten for the `prod`
        flavor -->
        <meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
        <!-- Disable collection of AD_ID for all build variants -->
        <meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
        <!-- Firebase automatically adds the following property which we don't use so remove it -->
        <property
            android:name="android.adservices.AD_SERVICES_CONFIG"
            tools:node="remove" />

    </application>

</manifest>


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.metrics.performance.JankStats
import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
import com.google.samples.apps.nowinandroid.core.analytics.LocalAnalyticsHelper
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.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone
import com.google.samples.apps.nowinandroid.ui.NiaApp
import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState
import com.google.samples.apps.nowinandroid.util.isSystemInDarkTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    /**
     * Lazily inject [JankStats], which is used to track jank throughout the app.
     */
    @Inject
    lateinit var lazyStats: dagger.Lazy<JankStats>

    @Inject
    lateinit var networkMonitor: NetworkMonitor

    @Inject
    lateinit var timeZoneMonitor: TimeZoneMonitor

    @Inject
    lateinit var analyticsHelper: AnalyticsHelper

    @Inject
    lateinit var userNewsResourceRepository: UserNewsResourceRepository

    private val viewModel: MainActivityViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        val splashScreen = installSplashScreen()
        super.onCreate(savedInstanceState)

        // We keep this as a mutable state, so that we can track changes inside the composition.
        // This allows us to react to dark/light mode changes.
        var themeSettings by mutableStateOf(
            ThemeSettings(
                darkTheme = resources.configuration.isSystemInDarkTheme,
                androidTheme = Loading.shouldUseAndroidTheme,
                disableDynamicTheming = Loading.shouldDisableDynamicTheming,
            ),
        )

        // Update the uiState
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                combine(
                    isSystemInDarkTheme(),
                    viewModel.uiState,
                ) { systemDark, uiState ->
                    ThemeSettings(
                        darkTheme = uiState.shouldUseDarkTheme(systemDark),
                        androidTheme = uiState.shouldUseAndroidTheme,
                        disableDynamicTheming = uiState.shouldDisableDynamicTheming,
                    )
                }
                    .onEach { themeSettings = it }
                    .map { it.darkTheme }
                    .distinctUntilChanged()
                    .collect { darkTheme ->
                        trace("niaEdgeToEdge") {
                            // Turn off the decor fitting system windows, which allows us to handle insets,
                            // including IME animations, and go edge-to-edge.
                            // This is the same parameters as the default enableEdgeToEdge call, but we manually
                            // resolve whether or not to show dark theme using uiState, since it can be different
                            // than the configuration's dark theme value based on the user preference.
                            enableEdgeToEdge(
                                statusBarStyle = SystemBarStyle.auto(
                                    lightScrim = android.graphics.Color.TRANSPARENT,
                                    darkScrim = android.graphics.Color.TRANSPARENT,
                                ) { darkTheme },
                                navigationBarStyle = SystemBarStyle.auto(
                                    lightScrim = lightScrim,
                                    darkScrim = darkScrim,
                                ) { darkTheme },
                            )
                        }
                    }
            }
        }

        // Keep the splash screen on-screen until the UI state is loaded. This condition is
        // evaluated each time the app needs to be redrawn so it should be fast to avoid blocking
        // the UI.
        splashScreen.setKeepOnScreenCondition { viewModel.uiState.value.shouldKeepSplashScreen() }

        setContent {
            val appState = rememberNiaAppState(
                networkMonitor = networkMonitor,
                userNewsResourceRepository = userNewsResourceRepository,
                timeZoneMonitor = timeZoneMonitor,
            )

            val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle()

            CompositionLocalProvider(
                LocalAnalyticsHelper provides analyticsHelper,
                LocalTimeZone provides currentTimeZone,
            ) {
                NiaTheme(
                    darkTheme = themeSettings.darkTheme,
                    androidTheme = themeSettings.androidTheme,
                    disableDynamicTheming = themeSettings.disableDynamicTheming,
                ) {
                    NiaApp(appState)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        lazyStats.get().isTrackingEnabled = true
    }

    override fun onPause() {
        super.onPause()
        lazyStats.get().isTrackingEnabled = false
    }
}

/**
 * The default light scrim, as defined by androidx and the platform:
 * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598
 */
private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)

/**
 * The default dark scrim, as defined by androidx and the platform:
 * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
 */
private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)

/**
 * Class for the system theme settings.
 * This wrapping class allows us to combine all the changes and prevent unnecessary recompositions.
 */
data class ThemeSettings(
    val darkTheme: Boolean,
    val androidTheme: Boolean,
    val disableDynamicTheming: Boolean,
)


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.MainActivityUiState.Success
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
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.core.model.data.UserData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

@HiltViewModel
class MainActivityViewModel @Inject constructor(
    userDataRepository: UserDataRepository,
) : ViewModel() {
    val uiState: StateFlow<MainActivityUiState> = userDataRepository.userData.map {
        Success(it)
    }.stateIn(
        scope = viewModelScope,
        initialValue = Loading,
        started = SharingStarted.WhileSubscribed(5_000),
    )
}

sealed interface MainActivityUiState {
    data object Loading : MainActivityUiState

    data class Success(val userData: UserData) : MainActivityUiState {
        override val shouldDisableDynamicTheming = !userData.useDynamicColor

        override val shouldUseAndroidTheme: Boolean = when (userData.themeBrand) {
            ThemeBrand.DEFAULT -> false
            ThemeBrand.ANDROID -> true
        }

        override fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) =
            when (userData.darkThemeConfig) {
                DarkThemeConfig.FOLLOW_SYSTEM -> isSystemDarkTheme
                DarkThemeConfig.LIGHT -> false
                DarkThemeConfig.DARK -> true
            }
    }

    /**
     * Returns `true` if the state wasn't loaded yet and it should keep showing the splash screen.
     */
    fun shouldKeepSplashScreen() = this is Loading

    /**
     * Returns `true` if the dynamic color is disabled.
     */
    val shouldDisableDynamicTheming: Boolean get() = true

    /**
     * Returns `true` if the Android theme should be used.
     */
    val shouldUseAndroidTheme: Boolean get() = false

    /**
     * Returns `true` if dark theme should be used.
     */
    fun shouldUseDarkTheme(isSystemDarkTheme: Boolean) = isSystemDarkTheme
}


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid

import android.app.Application
import android.content.pm.ApplicationInfo
import android.os.StrictMode
import android.os.StrictMode.ThreadPolicy.Builder
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

/**
 * [Application] class for NiA
 */
@HiltAndroidApp
class NiaApplication : Application(), ImageLoaderFactory {
    @Inject
    lateinit var imageLoader: dagger.Lazy<ImageLoader>

    @Inject
    lateinit var profileVerifierLogger: ProfileVerifierLogger

    override fun onCreate() {
        super.onCreate()

        setStrictModePolicy()

        // 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()

    /**
     * Return true if the application is debuggable.
     */
    private fun isDebuggable(): Boolean {
        return 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
    }

    /**
     * Set a thread policy that detects all potential problems on the main thread, such as network
     * and disk access.
     *
     * If a problem is found, the offending call will be logged and the application will be killed.
     */
    private fun setStrictModePolicy() {
        if (isDebuggable()) {
            StrictMode.setThreadPolicy(
                Builder().detectAll().penaltyLog().build(),
            )
        }
    }
}


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.di

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
import dagger.hilt.android.components.ActivityComponent

@Module
@InstallIn(ActivityComponent::class)
object JankStatsModule {
    @Provides
    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 = activity.window

    @Provides
    fun providesJankStats(
        window: Window,
        frameListener: OnFrameListener,
    ): JankStats = JankStats.createAndTrack(window, frameListener)
}


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelNavItem.kt
================================================
/*
 * Copyright 2025 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.navigation

import androidx.annotation.StringRes
import androidx.compose.ui.graphics.vector.ImageVector
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.api.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsNavKey
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R as bookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.api.R as forYouR
import com.google.samples.apps.nowinandroid.feature.search.api.R as searchR

/**
 * Type for the top level navigation items in the application. Contains UI information about the
 * current route that is used in the top app bar and common navigation UI.
 *
 * @param selectedIcon The icon to be displayed in the navigation UI when this destination is
 * selected.
 * @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
 * not selected.
 * @param iconTextId Text that to be displayed in the navigation UI.
 * @param titleTextId Text that is displayed on the top app bar.
 */
data class TopLevelNavItem(
    val selectedIcon: ImageVector,
    val unselectedIcon: ImageVector,
    @StringRes val iconTextId: Int,
    @StringRes val titleTextId: Int,
)

val FOR_YOU = TopLevelNavItem(
    selectedIcon = NiaIcons.Upcoming,
    unselectedIcon = NiaIcons.UpcomingBorder,
    iconTextId = forYouR.string.feature_foryou_api_title,
    titleTextId = R.string.app_name,
)

val BOOKMARKS = TopLevelNavItem(
    selectedIcon = NiaIcons.Bookmarks,
    unselectedIcon = NiaIcons.BookmarksBorder,
    iconTextId = bookmarksR.string.feature_bookmarks_api_title,
    titleTextId = bookmarksR.string.feature_bookmarks_api_title,
)

val INTERESTS = TopLevelNavItem(
    selectedIcon = NiaIcons.Grid3x3,
    unselectedIcon = NiaIcons.Grid3x3,
    iconTextId = searchR.string.feature_search_api_interests,
    titleTextId = searchR.string.feature_search_api_interests,
)

val TOP_LEVEL_NAV_ITEMS = mapOf(
    ForYouNavKey to FOR_YOU,
    BookmarksNavKey to BOOKMARKS,
    InterestsNavKey(null) to INTERESTS,
)


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.exclude
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration.Indefinite
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationSuiteScaffold
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopAppBar
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColors
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.navigation.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.toEntries
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.bookmarksEntry
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYouEntry
import com.google.samples.apps.nowinandroid.feature.interests.impl.navigation.interestsEntry
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchNavKey
import com.google.samples.apps.nowinandroid.feature.search.impl.navigation.searchEntry
import com.google.samples.apps.nowinandroid.feature.settings.impl.SettingsDialog
import com.google.samples.apps.nowinandroid.feature.topic.impl.navigation.topicEntry
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_NAV_ITEMS
import com.google.samples.apps.nowinandroid.feature.settings.impl.R as settingsR

@Composable
fun NiaApp(
    appState: NiaAppState,
    modifier: Modifier = Modifier,
    windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
    val shouldShowGradientBackground = appState.navigationState.currentTopLevelKey == ForYouNavKey
    var showSettingsDialog by rememberSaveable { mutableStateOf(false) }

    NiaBackground(modifier = modifier) {
        NiaGradientBackground(
            gradientColors = if (shouldShowGradientBackground) {
                LocalGradientColors.current
            } else {
                GradientColors()
            },
        ) {
            val snackbarHostState = remember { SnackbarHostState() }

            val isOffline by appState.isOffline.collectAsStateWithLifecycle()

            // If user is not connected to the internet show a snack bar to inform them.
            val notConnectedMessage = stringResource(R.string.not_connected)
            LaunchedEffect(isOffline) {
                if (isOffline) {
                    snackbarHostState.showSnackbar(
                        message = notConnectedMessage,
                        duration = Indefinite,
                    )
                }
            }
            CompositionLocalProvider(LocalSnackbarHostState provides snackbarHostState) {
                NiaApp(
                    appState = appState,

                    // TODO: Settings should be a dialog screen
                    showSettingsDialog = showSettingsDialog,
                    onSettingsDismissed = { showSettingsDialog = false },
                    onTopAppBarActionClick = { showSettingsDialog = true },
                    windowAdaptiveInfo = windowAdaptiveInfo,
                )
            }
        }
    }
}

@Composable
@OptIn(
    ExperimentalMaterial3Api::class,
    ExperimentalComposeUiApi::class,
    ExperimentalMaterial3AdaptiveApi::class,
)
internal fun NiaApp(
    appState: NiaAppState,
    showSettingsDialog: Boolean,
    onSettingsDismissed: () -> Unit,
    onTopAppBarActionClick: () -> Unit,
    modifier: Modifier = Modifier,
    windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
) {
    val unreadNavKeys by appState.topLevelNavKeysWithUnreadResources
        .collectAsStateWithLifecycle()

    if (showSettingsDialog) {
        SettingsDialog(
            onDismiss = { onSettingsDismissed() },
        )
    }

    val snackbarHostState = LocalSnackbarHostState.current

    val navigator = remember { Navigator(appState.navigationState) }

    NiaNavigationSuiteScaffold(
        navigationSuiteItems = {
            TOP_LEVEL_NAV_ITEMS.forEach { (navKey, navItem) ->
                val hasUnread = unreadNavKeys.contains(navKey)
                val selected = navKey == appState.navigationState.currentTopLevelKey
                item(
                    selected = selected,
                    onClick = { navigator.navigate(navKey) },
                    icon = {
                        Icon(
                            imageVector = navItem.unselectedIcon,
                            contentDescription = null,
                        )
                    },
                    selectedIcon = {
                        Icon(
                            imageVector = navItem.selectedIcon,
                            contentDescription = null,
                        )
                    },
                    label = { Text(stringResource(navItem.iconTextId)) },
                    modifier = Modifier
                        .testTag("NiaNavItem")
                        .then(if (hasUnread) Modifier.notificationDot() else Modifier),
                )
            }
        },
        windowAdaptiveInfo = windowAdaptiveInfo,
    ) {
        Scaffold(
            modifier = modifier.semantics {
                testTagsAsResourceId = true
            },
            containerColor = Color.Transparent,
            contentColor = MaterialTheme.colorScheme.onBackground,
            contentWindowInsets = WindowInsets(0, 0, 0, 0),
            snackbarHost = {
                SnackbarHost(
                    snackbarHostState,
                    modifier = Modifier.windowInsetsPadding(
                        WindowInsets.safeDrawing.exclude(
                            WindowInsets.ime,
                        ),
                    ),
                )
            },
        ) { padding ->
            Column(
                Modifier
                    .fillMaxSize()
                    .padding(padding)
                    .consumeWindowInsets(padding)
                    .windowInsetsPadding(
                        WindowInsets.safeDrawing.only(
                            WindowInsetsSides.Horizontal,
                        ),
                    ),
            ) {
                // Only show the top app bar on top level destinations.
                var shouldShowTopAppBar = false

                if (appState.navigationState.currentKey in appState.navigationState.topLevelKeys) {
                    shouldShowTopAppBar = true

                    val destination = TOP_LEVEL_NAV_ITEMS[appState.navigationState.currentTopLevelKey]
                        ?: error("Top level nav item not found for ${appState.navigationState.currentTopLevelKey}")

                    NiaTopAppBar(
                        titleRes = destination.titleTextId,
                        navigationIcon = NiaIcons.Search,
                        navigationIconContentDescription = stringResource(
                            id = settingsR.string.feature_settings_impl_top_app_bar_navigation_icon_description,
                        ),
                        actionIcon = NiaIcons.Settings,
                        actionIconContentDescription = stringResource(
                            id = settingsR.string.feature_settings_impl_top_app_bar_action_icon_description,
                        ),
                        colors = TopAppBarDefaults.topAppBarColors(
                            containerColor = Color.Transparent,
                        ),
                        onActionClick = { onTopAppBarActionClick() },
                        onNavigationClick = { navigator.navigate(SearchNavKey) },
                    )
                }

                Box(
                    // Workaround for https://issuetracker.google.com/338478720
                    modifier = Modifier.consumeWindowInsets(
                        if (shouldShowTopAppBar) {
                            WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
                        } else {
                            WindowInsets(0, 0, 0, 0)
                        },
                    ),
                ) {
                    val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()

                    val entryProvider = entryProvider {
                        forYouEntry(navigator)
                        bookmarksEntry(navigator)
                        interestsEntry(navigator)
                        topicEntry(navigator)
                        searchEntry(navigator)
                    }

                    NavDisplay(
                        entries = appState.navigationState.toEntries(entryProvider),
                        sceneStrategy = listDetailStrategy,
                        onBack = { navigator.goBack() },
                    )
                }

                // TODO: We may want to add padding or spacer when the snackbar is shown so that
                //  content doesn't display behind it.
            }
        }
    }
}

private fun Modifier.notificationDot(): Modifier =
    composed {
        val tertiaryColor = MaterialTheme.colorScheme.tertiary
        drawWithContent {
            drawContent()
            drawCircle(
                tertiaryColor,
                radius = 5.dp.toPx(),
                // This is based on the dimensions of the NavigationBar's "indicator pill";
                // however, its parameters are private, so we must depend on them implicitly
                // (NavigationBarTokens.ActiveIndicatorWidth = 64.dp)
                center = center + Offset(
                    64.dp.toPx() * .45f,
                    32.dp.toPx() * -.45f - 6.dp.toPx(),
                ),
            )
        }
    }


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt
================================================
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation3.runtime.NavKey
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.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.navigation.NavigationState
import com.google.samples.apps.nowinandroid.core.navigation.rememberNavigationState
import com.google.samples.apps.nowinandroid.core.ui.TrackDisposableJank
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksNavKey
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouNavKey
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_NAV_ITEMS
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.datetime.TimeZone

@Composable
fun rememberNiaAppState(
    networkMonitor: NetworkMonitor,
    userNewsResourceRepository: UserNewsResourceRepository,
    timeZoneMonitor: TimeZoneMonitor,
    coroutineScope: CoroutineScope = rememberCoroutineScope(),
): NiaAppState {
    val navigationState = rememberNavigationState(ForYouNavKey, TOP_LEVEL_NAV_ITEMS.keys)

    NavigationTrackingSideEffect(navigationState)

    return remember(
        navigationState,
        coroutineScope,
        networkMonitor,
        userNewsResourceRepository,
        timeZoneMonitor,
    ) {
        NiaAppState(
            navigationState = navigationState,
            coroutineScope = coroutineScope,
            networkMonitor = networkMonitor,
            userNewsResourceRepository = userNewsResourceRepository,
            timeZoneMonitor = timeZoneMonitor,
        )
    }
}

@Stable
class NiaAppState(
    val navigationState: NavigationState,
    coroutineScope: CoroutineScope,
    networkMonitor: NetworkMonitor,
    userNewsResourceRepository: UserNewsResourceRepository,
    timeZoneMonitor: TimeZoneMonitor,
) {
    val isOffline = networkMonitor.isOnline
        .map(Boolean::not)
        .stateIn(
            scope = coroutineScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = false,
        )

    /**
     * The top level nav keys that have unread news resources.
     */
    val topLevelNavKeysWithUnreadResources: StateFlow<Set<NavKey>> =
        userNewsResourceRepository.observeAllForFollowedTopics()
            .combine(userNewsResourceRepository.observeAllBookmarked()) { forYouNewsResources, bookmarkedNewsResources ->
                setOfNotNull(
                    ForYouNavKey.takeIf { forYouNewsResources.any { !it.hasBeenViewed } },
                    BookmarksNavKey.takeIf { bookmarkedNewsResources.any { !it.hasBeenViewed } },
                )
            }
            .stateIn(
                coroutineScope,
                SharingStarted.WhileSubscribed(5_000),
                initialValue = emptySet(),
            )

    val currentTimeZone = timeZoneMonitor.currentTimeZone
        .stateIn(
            coroutineScope,
            SharingStarted.WhileSubscribed(5_000),
            TimeZone.currentSystemDefault(),
        )
}

/**
 * Stores information about navigation events to be used with JankStats
 */
@Composable
private fun NavigationTrackingSideEffect(navigationState: NavigationState) {
    TrackDisposableJank(navigationState.currentKey) { metricsHolder ->
        metricsHolder.state?.putState("Navigation", navigationState.currentKey.toString())
        onDispose {}
    }
}


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt
================================================
/*
 * 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.common.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"
            },
        )
    }
}


================================================
FILE: app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/UiExtensions.kt
================================================
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.util

import android.content.res.Configuration
import androidx.activity.ComponentActivity
import androidx.core.util.Consumer
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged

/**
 * Convenience wrapper for dark mode checking
 */
val Configuration.isSystemInDarkTheme
    get() = (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES

/**
 * Registers listener for configuration changes to retrieve whether system is in dark theme or not.
 * Immediately upon subscribing, it sends the current value and then registers listener for changes.
 */
fun ComponentActivity.isSystemInDarkTheme() = callbackFlow {
    channel.trySend(resources.configuration.isSystemInDarkTheme)

    val listener = Consumer<Configuration> {
        channel.trySend(it.isSystemInDarkTheme)
    }

    addOnConfigurationChangedListener(listener)

    awaitClose { removeOnConfigurationChangedListener(listener) }
}
    .distinctUntilChanged()
    .conflate()


================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
  <path
      android:pathData="M0,0h108v108h-108z"
      android:fillColor="@color/ic_launcher_background_tint"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
  <path
      android:pathData="M65.08,84.13a1.94,1.94 0,1 1,-0.01 -3.9,1.94 1.94,0 0,1 0.01,3.9ZM43.6,84.13a1.94,1.94 0,1 1,-0.01 -3.9,1.94 1.94,0 0,1 0.01,3.9ZM65.77,72.44 L69.66,65.73a0.81,0.81 0,0 0,-0.3 -1.1,0.82 0.82,0 0,0 -1.11,0.3l-3.93,6.8a24,24 0,0 0,-9.99 -2.14c-3.6,0 -6.98,0.77 -9.99,2.14l-3.93,-6.8a0.8,0.8 0,1 0,-1.4 0.8l3.88,6.71A22.91,22.91 0,0 0,31 90.77h46.67a22.9,22.9 0,0 0,-11.9 -18.33Z"
      android:fillColor="@color/ic_launcher_foreground_tint"/>
  <path
      android:pathData="M46.57,35a0.85,0.85 0,0 0,-0.85 0.85v7.3h-1.53a1.52,1.52 0,0 0,0 3.05h1.53v-3.05h1.7c0.75,0 1.36,-0.61 1.36,-1.36v-4.07h1.19c0.46,0 0.84,-0.38 0.84,-0.85v-1.02a0.85,0.85 0,0 0,-0.84 -0.85h-3.4ZM46.57,54.35h3.4c0.46,0 0.84,-0.38 0.84,-0.85v-1.02a0.85,0.85 0,0 0,-0.84 -0.84h-1.19v-4.08c0,-0.75 -0.61,-1.36 -1.36,-1.36h-1.7v7.3c0,0.47 0.38,0.85 0.85,0.85ZM61.54,35c0.47,0 0.85,0.38 0.85,0.85v7.3h1.53a1.52,1.52 0,0 1,0 3.05h-1.53v-3.05h-1.7c-0.75,0 -1.36,-0.61 -1.36,-1.36v-4.07h-1.18a0.85,0.85 0,0 1,-0.85 -0.85v-1.02c0,-0.47 0.38,-0.85 0.85,-0.85h3.39ZM61.54,54.35h-3.39a0.85,0.85 0,0 1,-0.85 -0.85v-1.02c0,-0.46 0.38,-0.84 0.85,-0.84h1.18v-4.08c0,-0.75 0.61,-1.36 1.36,-1.36h1.7v7.3c0,0.47 -0.38,0.85 -0.85,0.85Z"
      android:fillColor="@color/ic_launcher_foreground_tint"
      android:fillType="evenOdd"/>
</vector>


================================================
FILE: app/src/main/res/drawable/ic_splash.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">

    <path
        android:pathData="M0,0h108v108h-108z"
        android:fillColor="@color/ic_launcher_background_tint"/>
    <path
        android:pathData="M65.08,84.13a1.94,1.94 0,1 1,-0.01 -3.9,1.94 1.94,0 0,1 0.01,3.9ZM43.6,84.13a1.94,1.94 0,1 1,-0.01 -3.9,1.94 1.94,0 0,1 0.01,3.9ZM65.77,72.44 L69.66,65.73a0.81,0.81 0,0 0,-0.3 -1.1,0.82 0.82,0 0,0 -1.11,0.3l-3.93,6.8a24,24 0,0 0,-9.99 -2.14c-3.6,0 -6.98,0.77 -9.99,2.14l-3.93,-6.8a0.8,0.8 0,1 0,-1.4 0.8l3.88,6.71A22.91,22.91 0,0 0,31 90.77h46.67a22.9,22.9 0,0 0,-11.9 -18.33Z"
        android:fillColor="@color/ic_launcher_foreground_tint"/>
    <path
        android:pathData="M46.57,35a0.85,0.85 0,0 0,-0.85 0.85v7.3h-1.53a1.52,1.52 0,0 0,0 3.05h1.53v-3.05h1.7c0.75,0 1.36,-0.61 1.36,-1.36v-4.07h1.19c0.46,0 0.84,-0.38 0.84,-0.85v-1.02a0.85,0.85 0,0 0,-0.84 -0.85h-3.4ZM46.57,54.35h3.4c0.46,0 0.84,-0.38 0.84,-0.85v-1.02a0.85,0.85 0,0 0,-0.84 -0.84h-1.19v-4.08c0,-0.75 -0.61,-1.36 -1.36,-1.36h-1.7v7.3c0,0.47 0.38,0.85 0.85,0.85ZM61.54,35c0.47,0 0.85,0.38 0.85,0.85v7.3h1.53a1.52,1.52 0,0 1,0 3.05h-1.53v-3.05h-1.7c-0.75,0 -1.36,-0.61 -1.36,-1.36v-4.07h-1.18a0.85,0.85 0,0 1,-0.85 -0.85v-1.02c0,-0.47 0.38,-0.85 0.85,-0.85h3.39ZM61.54,54.35h-3.39a0.85,0.85 0,0 1,-0.85 -0.85v-1.02c0,-0.46 0.38,-0.84 0.85,-0.84h1.18v-4.08c0,-0.75 0.61,-1.36 1.36,-1.36h1.7v7.3c0,0.47 -0.38,0.85 -0.85,0.85Z"
        android:fillColor="@color/ic_launcher_foreground_tint"
        android:fillType="evenOdd"/>

</vector>


================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>


================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2022 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://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.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>


================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<resources>
    <color name="ic_launcher_background_tint">#000000</color>
    <color name="ic_launcher_foreground_tint">#FCFCFC</color>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<resources>
    <string name="app_name">Now in Android</string>
    <string name="not_connected">⚠️ You aren’t connected to the internet</string>
</resources>


================================================
FILE: app/src/main/res/values/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<resources xmlns:tools="http://schemas.android.com/tools">

    <!-- Allows us to override night specific attributes in the
         values-night folder. -->
    <style name="NightAdjusted.Theme.Nia" parent="android:Theme.Material.Light.NoActionBar" />

    <!-- The final theme we use -->
    <style name="Theme.Nia" parent="NightAdjusted.Theme.Nia">
        <item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
    </style>

    <style name="NightAdjusted.Theme.Splash" parent="Theme.SplashScreen">
        <item name="android:windowLightStatusBar" tools:targetApi="23">true</item>
        <item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
    </style>

    <style name="Theme.Nia.Splash" parent="NightAdjusted.Theme.Splash">
        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash</item>
        <item name="postSplashScreenTheme">@style/Theme.Nia</item>
    </style>

</resources>


================================================
FILE: app/src/main/res/values-night/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<resources>
    <color name="ic_launcher_background_tint">#FCFCFC</color>
    <color name="ic_launcher_foreground_tint">#000000</color>
</resources>


================================================
FILE: app/src/main/res/values-night/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright 2021 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

          http://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.
-->
<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="NightAdjusted.Theme.Nia" parent="android:Theme.Material.NoActionBar" />

    <style name="NightAdjusted.Theme.Splash" parent="Theme.SplashScreen">
        <item name="android:windowLightStatusBar" tools:targetApi="23">false</item>
        <item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
    </style>

</resources>


================================================
FILE: app/src/prod/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
     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

          http://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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application>
        <!-- Enable Firebase analytics for `prod` builds -->
        <meta-data
            tools:replace="android:value"
            android:name="firebase_analytics_collection_deactivated"
            android:value="false" />
    </application>
</manifest>


================================================
FILE: app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/DeviceConfigurationOverrideWindowInsets.kt
================================================
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import android.view.WindowInsets
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.test.DeviceConfigurationOverride
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.children

/**
 * A [DeviceConfigurationOverride] that overrides the window insets for the contained content.
 */
@Suppress("ktlint:standard:function-naming")
fun DeviceConfigurationOverride.Companion.WindowInsets(
    windowInsets: WindowInsetsCompat,
): DeviceConfigurationOverride = DeviceConfigurationOverride { contentUnderTest ->
    val currentContentUnderTest by rememberUpdatedState(contentUnderTest)
    val currentWindowInsets by rememberUpdatedState(windowInsets)
    AndroidView(
        factory = { context ->
            object : AbstractComposeView(context) {
                @Composable
                override fun Content() {
                    currentContentUnderTest()
                }

                override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
                    children.forEach {
                        it.dispatchApplyWindowInsets(
                            WindowInsets(currentWindowInsets.toWindowInsets()),
                        )
                    }
                    return WindowInsetsCompat.CONSUMED.toWindowInsets()!!
                }

                /**
                 * Deprecated, but intercept the `requestApplyInsets` call via the deprecated
                 * method.
                 */
                @Deprecated("Deprecated in Java")
                override fun requestFitSystemWindows() {
                    dispatchApplyWindowInsets(WindowInsets(currentWindowInsets.toWindowInsets()!!))
                }
            }
        },
        update = { with(currentWindowInsets) { it.requestApplyInsets() } },
    )
}


================================================
FILE: app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt
================================================
/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.samples.apps.nowinandroid.ui

import androidx.compose.material3.adaptive.Posture
import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.test.DeviceConfigurationOverride
import androidx.compose.ui.test.ForcedSize
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.window.core.layout.WindowSizeClass
import com.github.takahirom.roborazzi.captureRoboImage
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository
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.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
import org.robolectric.annotation.LooperMode
import java.util.TimeZone
import javax.inject.Inject

/**
 * Tests that the navigation UI is rendered correctly on different screen sizes.
 */
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(application = HiltTestApplication::class, qualifiers = "w1000dp-h1000dp-480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
@HiltAndroidTest
class NiaAppScreenSizesScreenshotTests {

    /**
     * Manages the components' state and is used to perform injection on your test
     */
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    /**
     * Use a test activity to set the content on.
     */
    @get:Rule(order = 1)
    val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()

    @Inject
    lateinit var networkMonitor: NetworkMonitor

    @Inject
    lateinit var timeZoneMonitor: TimeZoneMonitor

    @Inject
    lateinit var userDataRepository: UserDataRepository

    @Inject
    lateinit var topicsRepository: TopicsRepository

    @Inject
    lateinit var userNewsResourceRepository: UserNewsResourceRepository

    @Before
    fun setup() {
        hiltRule.inject()

        // Configure user data
        runBlocking {
            userDataRepository.setShouldHideOnboarding(true)

            userDataRepository.setFollowedTopicIds(
                setOf(topicsRepository.getTopics().first().first().id),
            )
        }
    }

    @Before
    fun setTimeZone() {
        // Make time zone deterministic in tests
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
    }

    private fun testNiaAppScreenshotWithSize(width: Dp, height: Dp, screenshotName: String) {
        composeTestRule.setContent {
            CompositionLocalProvider(
                LocalInspectionMode provides true,
            ) {
                DeviceConfigurationOverride(
                    override = DeviceConfigurationOverride.ForcedSize(DpSize(width, height)),
                ) {
                    NiaTheme {
                        val fakeAppState = rememberNiaAppState(
                            networkMonitor = networkMonitor,
                            userNewsResourceRepository = userNewsResourceRepository,
                            timeZoneMonitor = timeZoneMonitor,
                        )
                        NiaApp(
                            fakeAppState,
                            windowAdaptiveInfo = WindowAdaptiveInfo(
                                windowSizeClass = WindowSizeClass.compute(
                                    width.value,
                                    height.value,
                                ),
                                windowPosture = Posture(),
                            ),
                        )
                    }
                }
            }
        }

        composeTestRule.onRoot()
            .captureRoboImage(
                "src/testDemo/screenshots/$screenshotName.png",
                roborazziOptions = DefaultRoborazziOptions,
            )
    }

    @Test
    fun compactWidth_compactHeight_showsNavigationBar() {
        testNiaAppScreenshotWithSize(
            400.dp,
            400.dp,
            "compactWidth_compactHeight_showsNavigationBar",
        )
    }

    @Test
    fun mediumWidth_compactHeight_showsNavigationBar() {
        testNiaAppScreenshotWithSize(
            610.dp,
            400.dp,
            "mediumWidth_compactHeight_showsNavigationBar",
        )
    }

    @Test
    fun expandedWidth_compactHeight_showsNavigationBar() {
        testNiaAppScreenshotWithSize(
            900.dp,
            400.dp,
            "expandedWidth_compactHeight_showsNavigationBar",
        )
    }

    @Test
    fun compactWidth_mediumHeight_showsNavigationBar() {
        testNiaAppScreenshotWithSize(
            400.dp,
            500.dp,
            "compactWidth_mediumHeight_showsNavigationBar",
        )
    }

    @Test
    fun mediumWidth_mediumHeight_showsNavigationRail() {
        testNiaAppScreenshotWithSize(
            610.dp,
            500.dp,
            "mediumWidth_mediumHeight_showsNavigationRail",
        )
    }

    @Test
    fun expandedWidth_mediumHeight_showsNavigationRail() {
        testNiaAppScreenshotWithSize(
            900.dp,
            500.dp,
            "expandedWidth_mediumHeight_showsNavigationRail",
        )
    }

    @Test
    fun compactWidth_expandedHeight_showsNavigationBar() {
        testNiaAppScreenshotWithSize(
            400.dp,
            1000.dp,
            "compactWidth_expandedHeight_showsNavigationBar",
        )
    }

    @Test
    fun mediumWidth_expandedHeight_showsNavigationRail() {
        testNiaAppScreenshotWithSize(
            610.dp,
            1000.dp,
            "mediumWidth_expandedHeight_showsNavigationRail",
        )
    }

    @Test
    fun expandedWidth_expandedHeight_sho
Download .txt
gitextract_tu9jpf5p/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── docs_issue.yml
│   │   └── feature_request.yml
│   ├── ci-gradle.properties
│   ├── pull_request_template.md
│   ├── renovate.json
│   └── workflows/
│       ├── Build.yaml
│       ├── NightlyBaselineProfiles.yaml
│       └── Release.yml
├── .gitignore
├── .google/
│   ├── BUILDME
│   └── packaging.yaml
├── .idea/
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   └── copyright/
│       ├── The_Android_Open_Source_Project.xml
│       └── profiles_settings.xml
├── .run/
│   ├── Generate Demo Baseline Profile.run.xml
│   └── spotlessApply.run.xml
├── AGENTS.md
├── CODEOWNERS
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── README.md
│   ├── benchmark-rules.pro
│   ├── build.gradle.kts
│   ├── dependencies/
│   │   └── prodReleaseRuntimeClasspath.txt
│   ├── google-services.json
│   ├── prodRelease-badging.txt
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── kotlin/
│       │       └── com/
│       │           └── google/
│       │               └── samples/
│       │                   └── apps/
│       │                       └── nowinandroid/
│       │                           └── ui/
│       │                               ├── NavigationTest.kt
│       │                               └── UiTestExtensions.kt
│       ├── benchmark/
│       │   └── res/
│       │       ├── values/
│       │       │   └── colors.xml
│       │       └── values-night/
│       │           └── colors.xml
│       ├── debug/
│       │   └── res/
│       │       ├── values/
│       │       │   └── colors.xml
│       │       └── values-night/
│       │           └── colors.xml
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── kotlin/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── samples/
│       │   │               └── apps/
│       │   │                   └── nowinandroid/
│       │   │                       ├── MainActivity.kt
│       │   │                       ├── MainActivityViewModel.kt
│       │   │                       ├── NiaApplication.kt
│       │   │                       ├── di/
│       │   │                       │   └── JankStatsModule.kt
│       │   │                       ├── navigation/
│       │   │                       │   └── TopLevelNavItem.kt
│       │   │                       ├── ui/
│       │   │                       │   ├── NiaApp.kt
│       │   │                       │   └── NiaAppState.kt
│       │   │                       └── util/
│       │   │                           ├── ProfileVerifierLogger.kt
│       │   │                           └── UiExtensions.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── ic_launcher_foreground.xml
│       │       │   └── ic_splash.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── values/
│       │       │   ├── colors.xml
│       │       │   ├── strings.xml
│       │       │   └── themes.xml
│       │       └── values-night/
│       │           ├── colors.xml
│       │           └── themes.xml
│       ├── prod/
│       │   └── AndroidManifest.xml
│       └── testDemo/
│           ├── kotlin/
│           │   └── com/
│           │       └── google/
│           │           └── samples/
│           │               └── apps/
│           │                   └── nowinandroid/
│           │                       └── ui/
│           │                           ├── DeviceConfigurationOverrideWindowInsets.kt
│           │                           ├── NiaAppScreenSizesScreenshotTests.kt
│           │                           ├── NiaAppStateTest.kt
│           │                           ├── SnackbarInsetsScreenshotTests.kt
│           │                           └── SnackbarScreenshotTests.kt
│           └── resources/
│               └── robolectric.properties
├── app-nia-catalog/
│   ├── .gitignore
│   ├── README.md
│   ├── build.gradle.kts
│   ├── dependencies/
│   │   └── releaseRuntimeClasspath.txt
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           ├── kotlin/
│           │   └── com/
│           │       └── google/
│           │           └── samples/
│           │               └── apps/
│           │                   └── niacatalog/
│           │                       ├── NiaCatalogActivity.kt
│           │                       └── ui/
│           │                           └── Catalog.kt
│           └── res/
│               ├── drawable/
│               │   ├── ic_launcher_background.xml
│               │   └── ic_launcher_foreground.xml
│               ├── mipmap-anydpi-v26/
│               │   ├── ic_launcher.xml
│               │   └── ic_launcher_round.xml
│               └── values/
│                   ├── strings.xml
│                   └── themes.xml
├── benchmarks/
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── AndroidManifest.xml
│           └── kotlin/
│               ├── androidx/
│               │   └── test/
│               │       └── uiautomator/
│               │           └── UiAutomatorHelpers.kt
│               └── com/
│                   └── google/
│                       └── samples/
│                           └── apps/
│                               └── nowinandroid/
│                                   ├── BaselineProfileMetrics.kt
│                                   ├── GeneralActions.kt
│                                   ├── Utils.kt
│                                   ├── baselineprofile/
│                                   │   ├── BookmarksBaselineProfile.kt
│                                   │   ├── ForYouBaselineProfile.kt
│                                   │   ├── InterestsBaselineProfile.kt
│                                   │   └── StartupBaselineProfile.kt
│                                   ├── bookmarks/
│                                   │   └── BookmarksActions.kt
│                                   ├── foryou/
│                                   │   ├── ForYouActions.kt
│                                   │   └── ScrollForYouFeedBenchmark.kt
│                                   ├── interests/
│                                   │   ├── InterestsActions.kt
│                                   │   ├── ScrollTopicListBenchmark.kt
│                                   │   ├── ScrollTopicListPowerMetricsBenchmark.kt
│                                   │   └── TopicsScreenRecompositionBenchmark.kt
│                                   └── startup/
│                                       └── StartupBenchmark.kt
├── build-logic/
│   ├── README.md
│   ├── convention/
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               ├── AndroidApplicationComposeConventionPlugin.kt
│   │               ├── AndroidApplicationConventionPlugin.kt
│   │               ├── AndroidApplicationFirebaseConventionPlugin.kt
│   │               ├── AndroidApplicationFlavorsConventionPlugin.kt
│   │               ├── AndroidApplicationJacocoConventionPlugin.kt
│   │               ├── AndroidFeatureApiConventionPlugin.kt
│   │               ├── AndroidFeatureImplConventionPlugin.kt
│   │               ├── AndroidLibraryComposeConventionPlugin.kt
│   │               ├── AndroidLibraryConventionPlugin.kt
│   │               ├── AndroidLibraryJacocoConventionPlugin.kt
│   │               ├── AndroidLintConventionPlugin.kt
│   │               ├── AndroidRoomConventionPlugin.kt
│   │               ├── AndroidTestConventionPlugin.kt
│   │               ├── HiltConventionPlugin.kt
│   │               ├── JvmLibraryConventionPlugin.kt
│   │               ├── RootPlugin.kt
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   ├── AndroidCompose.kt
│   │                                   ├── AndroidInstrumentedTests.kt
│   │                                   ├── Badging.kt
│   │                                   ├── GradleManagedDevices.kt
│   │                                   ├── Graph.kt
│   │                                   ├── Jacoco.kt
│   │                                   ├── KotlinAndroid.kt
│   │                                   ├── NiaBuildType.kt
│   │                                   ├── NiaFlavor.kt
│   │                                   ├── PrintTestApks.kt
│   │                                   ├── ProjectExtensions.kt
│   │                                   └── Spotless.kt
│   ├── gradle.properties
│   └── settings.gradle.kts
├── build.gradle.kts
├── build_android_release.sh
├── compose_compiler_config.conf
├── core/
│   ├── analytics/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── analytics/
│   │       │                                   └── AnalyticsModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── analytics/
│   │       │                                   ├── AnalyticsEvent.kt
│   │       │                                   ├── AnalyticsHelper.kt
│   │       │                                   ├── NoOpAnalyticsHelper.kt
│   │       │                                   ├── StubAnalyticsHelper.kt
│   │       │                                   └── UiHelpers.kt
│   │       └── prod/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── analytics/
│   │                                           ├── AnalyticsModule.kt
│   │                                           └── FirebaseAnalyticsHelper.kt
│   ├── common/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── common/
│   │       │                                   ├── network/
│   │       │                                   │   ├── NiaDispatchers.kt
│   │       │                                   │   └── di/
│   │       │                                   │       ├── CoroutineScopesModule.kt
│   │       │                                   │       └── DispatchersModule.kt
│   │       │                                   └── result/
│   │       │                                       └── Result.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── common/
│   │                                           └── result/
│   │                                               └── ResultKtTest.kt
│   ├── data/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── data/
│   │       │                                   ├── SyncUtilities.kt
│   │       │                                   ├── di/
│   │       │                                   │   ├── DataModule.kt
│   │       │                                   │   └── UserNewsResourceRepositoryModule.kt
│   │       │                                   ├── model/
│   │       │                                   │   ├── NewsResource.kt
│   │       │                                   │   ├── RecentSearchQuery.kt
│   │       │                                   │   └── Topic.kt
│   │       │                                   ├── repository/
│   │       │                                   │   ├── AnalyticsExtensions.kt
│   │       │                                   │   ├── CompositeUserNewsResourceRepository.kt
│   │       │                                   │   ├── DefaultRecentSearchRepository.kt
│   │       │                                   │   ├── DefaultSearchContentsRepository.kt
│   │       │                                   │   ├── NewsRepository.kt
│   │       │                                   │   ├── OfflineFirstNewsRepository.kt
│   │       │                                   │   ├── OfflineFirstTopicsRepository.kt
│   │       │                                   │   ├── OfflineFirstUserDataRepository.kt
│   │       │                                   │   ├── RecentSearchRepository.kt
│   │       │                                   │   ├── SearchContentsRepository.kt
│   │       │                                   │   ├── TopicsRepository.kt
│   │       │                                   │   ├── UserDataRepository.kt
│   │       │                                   │   └── UserNewsResourceRepository.kt
│   │       │                                   └── util/
│   │       │                                       ├── ConnectivityManagerNetworkMonitor.kt
│   │       │                                       ├── NetworkMonitor.kt
│   │       │                                       ├── SyncManager.kt
│   │       │                                       └── TimeZoneMonitor.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       ├── data/
│   │                                       │   ├── CompositeUserNewsResourceRepositoryTest.kt
│   │                                       │   ├── UserNewsResourceTest.kt
│   │                                       │   ├── model/
│   │                                       │   │   └── NetworkEntityTest.kt
│   │                                       │   ├── repository/
│   │                                       │   │   ├── OfflineFirstNewsRepositoryTest.kt
│   │                                       │   │   ├── OfflineFirstTopicsRepositoryTest.kt
│   │                                       │   │   ├── OfflineFirstUserDataRepositoryTest.kt
│   │                                       │   │   └── TestSynchronizer.kt
│   │                                       │   └── testdoubles/
│   │                                       │       ├── TestNewsResourceDao.kt
│   │                                       │       ├── TestNiaNetworkDataSource.kt
│   │                                       │       └── TestTopicDao.kt
│   │                                       └── database/
│   │                                           └── model/
│   │                                               └── PopulatedNewsResourceKtTest.kt
│   ├── data-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── data/
│   │                                           └── test/
│   │                                               ├── AlwaysOnlineNetworkMonitor.kt
│   │                                               ├── DefaultZoneIdTimeZoneMonitor.kt
│   │                                               ├── TestDataModule.kt
│   │                                               └── repository/
│   │                                                   ├── FakeNewsRepository.kt
│   │                                                   ├── FakeRecentSearchRepository.kt
│   │                                                   ├── FakeSearchContentsRepository.kt
│   │                                                   ├── FakeTopicsRepository.kt
│   │                                                   └── FakeUserDataRepository.kt
│   ├── database/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── schemas/
│   │   │   └── com.google.samples.apps.nowinandroid.core.database.NiaDatabase/
│   │   │       ├── 1.json
│   │   │       ├── 10.json
│   │   │       ├── 11.json
│   │   │       ├── 12.json
│   │   │       ├── 13.json
│   │   │       ├── 14.json
│   │   │       ├── 2.json
│   │   │       ├── 3.json
│   │   │       ├── 4.json
│   │   │       ├── 5.json
│   │   │       ├── 6.json
│   │   │       ├── 7.json
│   │   │       ├── 8.json
│   │   │       └── 9.json
│   │   └── src/
│   │       ├── androidTest/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── database/
│   │       │                                   └── dao/
│   │       │                                       ├── DatabaseTest.kt
│   │       │                                       ├── NewsResourceDaoTest.kt
│   │       │                                       └── TopicDaoTest.kt
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── database/
│   │                                           ├── DatabaseMigrations.kt
│   │                                           ├── NiaDatabase.kt
│   │                                           ├── dao/
│   │                                           │   ├── NewsResourceDao.kt
│   │                                           │   ├── NewsResourceFtsDao.kt
│   │                                           │   ├── RecentSearchQueryDao.kt
│   │                                           │   ├── TopicDao.kt
│   │                                           │   └── TopicFtsDao.kt
│   │                                           ├── di/
│   │                                           │   ├── DaosModule.kt
│   │                                           │   └── DatabaseModule.kt
│   │                                           ├── model/
│   │                                           │   ├── NewsResourceEntity.kt
│   │                                           │   ├── NewsResourceFtsEntity.kt
│   │                                           │   ├── NewsResourceTopicCrossRef.kt
│   │                                           │   ├── PopulatedNewsResource.kt
│   │                                           │   ├── RecentSearchQueryEntity.kt
│   │                                           │   ├── TopicEntity.kt
│   │                                           │   └── TopicFtsEntity.kt
│   │                                           └── util/
│   │                                               └── InstantConverter.kt
│   ├── datastore/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── consumer-proguard-rules.pro
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── datastore/
│   │       │                                   ├── ChangeListVersions.kt
│   │       │                                   ├── IntToStringIdsMigration.kt
│   │       │                                   ├── ListToMapMigration.kt
│   │       │                                   ├── NiaPreferencesDataSource.kt
│   │       │                                   ├── UserPreferencesSerializer.kt
│   │       │                                   └── di/
│   │       │                                       └── DataStoreModule.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── datastore/
│   │                                           ├── IntToStringIdsMigrationTest.kt
│   │                                           ├── ListToMapMigrationTest.kt
│   │                                           ├── NiaPreferencesDataSourceTest.kt
│   │                                           └── UserPreferencesSerializerTest.kt
│   ├── datastore-proto/
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── proto/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── data/
│   │                                       ├── dark_theme_config.proto
│   │                                       ├── theme_brand.proto
│   │                                       └── user_preferences.proto
│   ├── datastore-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── datastore/
│   │                                           └── test/
│   │                                               ├── InMemoryDataStore.kt
│   │                                               └── TestDataStoreModule.kt
│   ├── designsystem/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── kotlin/
│   │       │   │   └── com/
│   │       │   │       └── google/
│   │       │   │           └── samples/
│   │       │   │               └── apps/
│   │       │   │                   └── nowinandroid/
│   │       │   │                       └── core/
│   │       │   │                           └── designsystem/
│   │       │   │                               ├── component/
│   │       │   │                               │   ├── Background.kt
│   │       │   │                               │   ├── Button.kt
│   │       │   │                               │   ├── Chip.kt
│   │       │   │                               │   ├── DynamicAsyncImage.kt
│   │       │   │                               │   ├── IconButton.kt
│   │       │   │                               │   ├── LoadingWheel.kt
│   │       │   │                               │   ├── Navigation.kt
│   │       │   │                               │   ├── Tabs.kt
│   │       │   │                               │   ├── Tag.kt
│   │       │   │                               │   ├── TopAppBar.kt
│   │       │   │                               │   ├── ViewToggle.kt
│   │       │   │                               │   └── scrollbar/
│   │       │   │                               │       ├── AppScrollbars.kt
│   │       │   │                               │       ├── LazyScrollbarUtilities.kt
│   │       │   │                               │       ├── Scrollbar.kt
│   │       │   │                               │       ├── ScrollbarExt.kt
│   │       │   │                               │       └── ThumbExt.kt
│   │       │   │                               ├── icon/
│   │       │   │                               │   └── NiaIcons.kt
│   │       │   │                               └── theme/
│   │       │   │                                   ├── Background.kt
│   │       │   │                                   ├── Color.kt
│   │       │   │                                   ├── Gradient.kt
│   │       │   │                                   ├── Theme.kt
│   │       │   │                                   ├── Tint.kt
│   │       │   │                                   └── Type.kt
│   │       │   └── res/
│   │       │       └── drawable/
│   │       │           └── core_designsystem_ic_placeholder_default.xml
│   │       └── test/
│   │           ├── kotlin/
│   │           │   └── com/
│   │           │       └── google/
│   │           │           └── samples/
│   │           │               └── apps/
│   │           │                   └── nowinandroid/
│   │           │                       └── core/
│   │           │                           └── designsystem/
│   │           │                               ├── BackgroundScreenshotTests.kt
│   │           │                               ├── ButtonScreenshotTests.kt
│   │           │                               ├── FilterChipScreenshotTests.kt
│   │           │                               ├── IconButtonScreenshotTests.kt
│   │           │                               ├── LoadingWheelScreenshotTests.kt
│   │           │                               ├── NavigationScreenshotTests.kt
│   │           │                               ├── TabsScreenshotTests.kt
│   │           │                               ├── TagScreenshotTests.kt
│   │           │                               ├── ThemeTest.kt
│   │           │                               └── TopAppBarScreenshotTests.kt
│   │           └── resources/
│   │               └── robolectric.properties
│   ├── domain/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── domain/
│   │       │                                   ├── GetFollowableTopicsUseCase.kt
│   │       │                                   ├── GetRecentSearchQueriesUseCase.kt
│   │       │                                   └── GetSearchContentsUseCase.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── domain/
│   │                                           └── GetFollowableTopicsUseCaseTest.kt
│   ├── model/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── model/
│   │                                           └── data/
│   │                                               ├── DarkThemeConfig.kt
│   │                                               ├── FollowableTopic.kt
│   │                                               ├── NewsResource.kt
│   │                                               ├── SearchResult.kt
│   │                                               ├── ThemeBrand.kt
│   │                                               ├── Topic.kt
│   │                                               ├── UserData.kt
│   │                                               ├── UserNewsResource.kt
│   │                                               └── UserSearchResult.kt
│   ├── navigation/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── main/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── navigation/
│   │       │                                   ├── NavigationState.kt
│   │       │                                   └── Navigator.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── navigation/
│   │                                           └── NavigatorTest.kt
│   ├── network/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   ├── lint.xml
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   └── di/
│   │       │                                       └── FlavoredNetworkModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── assets/
│   │       │   │   ├── news.json
│   │       │   │   └── topics.json
│   │       │   └── kotlin/
│   │       │       ├── JvmUnitTestDemoAssetManager.kt
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   ├── NiaNetworkDataSource.kt
│   │       │                                   ├── demo/
│   │       │                                   │   ├── DemoAssetManager.kt
│   │       │                                   │   └── DemoNiaNetworkDataSource.kt
│   │       │                                   ├── di/
│   │       │                                   │   └── NetworkModule.kt
│   │       │                                   ├── model/
│   │       │                                   │   ├── NetworkChangeList.kt
│   │       │                                   │   ├── NetworkNewsResource.kt
│   │       │                                   │   └── NetworkTopic.kt
│   │       │                                   └── retrofit/
│   │       │                                       └── RetrofitNiaNetwork.kt
│   │       ├── prod/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── network/
│   │       │                                   └── di/
│   │       │                                       └── FlavoredNetworkModule.kt
│   │       └── test/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── network/
│   │                                           └── demo/
│   │                                               └── DemoNiaNetworkDataSourceTest.kt
│   ├── notifications/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       ├── demo/
│   │       │   └── kotlin/
│   │       │       └── com/
│   │       │           └── google/
│   │       │               └── samples/
│   │       │                   └── apps/
│   │       │                       └── nowinandroid/
│   │       │                           └── core/
│   │       │                               └── notifications/
│   │       │                                   └── NotificationsModule.kt
│   │       ├── main/
│   │       │   ├── AndroidManifest.xml
│   │       │   ├── kotlin/
│   │       │   │   └── com/
│   │       │   │       └── google/
│   │       │   │           └── samples/
│   │       │   │               └── apps/
│   │       │   │                   └── nowinandroid/
│   │       │   │                       └── core/
│   │       │   │                           └── notifications/
│   │       │   │                               ├── NoOpNotifier.kt
│   │       │   │                               ├── Notifier.kt
│   │       │   │                               └── SystemTrayNotifier.kt
│   │       │   └── res/
│   │       │       ├── drawable-anydpi-v24/
│   │       │       │   └── core_notifications_ic_nia_notification.xml
│   │       │       └── values/
│   │       │           └── strings.xml
│   │       └── prod/
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── notifications/
│   │                                           └── NotificationsModule.kt
│   ├── screenshot-testing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── testing/
│   │                                           └── util/
│   │                                               └── ScreenshotHelper.kt
│   ├── testing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       ├── rules/
│   │                                       │   └── GrantPostNotificationsPermissionRule.kt
│   │                                       └── testing/
│   │                                           ├── NiaTestRunner.kt
│   │                                           ├── data/
│   │                                           │   ├── FollowableTopicTestData.kt
│   │                                           │   ├── NewsResourcesTestData.kt
│   │                                           │   ├── TopicsTestData.kt
│   │                                           │   └── UserNewsResourcesTestData.kt
│   │                                           ├── di/
│   │                                           │   ├── TestDispatcherModule.kt
│   │                                           │   └── TestDispatchersModule.kt
│   │                                           ├── notifications/
│   │                                           │   └── TestNotifier.kt
│   │                                           ├── repository/
│   │                                           │   ├── TestNewsRepository.kt
│   │                                           │   ├── TestRecentSearchRepository.kt
│   │                                           │   ├── TestSearchContentsRepository.kt
│   │                                           │   ├── TestTopicsRepository.kt
│   │                                           │   └── TestUserDataRepository.kt
│   │                                           └── util/
│   │                                               ├── MainDispatcherRule.kt
│   │                                               ├── TestAnalyticsHelper.kt
│   │                                               ├── TestNetworkMonitor.kt
│   │                                               ├── TestSyncManager.kt
│   │                                               └── TestTimeZoneMonitor.kt
│   └── ui/
│       ├── .gitignore
│       ├── README.md
│       ├── build.gradle.kts
│       └── src/
│           ├── androidTest/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── core/
│           │                               └── ui/
│           │                                   └── NewsResourceCardTest.kt
│           └── main/
│               ├── AndroidManifest.xml
│               ├── kotlin/
│               │   └── com/
│               │       └── google/
│               │           └── samples/
│               │               └── apps/
│               │                   └── nowinandroid/
│               │                       └── core/
│               │                           └── ui/
│               │                               ├── AnalyticsExtensions.kt
│               │                               ├── DevicePreviews.kt
│               │                               ├── FollowableTopicPreviewParameterProvider.kt
│               │                               ├── InterestsItem.kt
│               │                               ├── JankStatsExtensions.kt
│               │                               ├── LocalTimeZone.kt
│               │                               ├── NewsFeed.kt
│               │                               ├── NewsResourceCard.kt
│               │                               ├── NewsResourceCardList.kt
│               │                               └── UserNewsResourcePreviewParameterProvider.kt
│               └── res/
│                   └── values/
│                       └── strings.xml
├── docs/
│   ├── ArchitectureLearningJourney.md
│   └── ModularizationLearningJourney.md
├── feature/
│   ├── bookmarks/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── bookmarks/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── BookmarksNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_bookmarks_api_mg_empty_bookmarks.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── bookmarks/
│   │           │                                   └── impl/
│   │           │                                       └── BookmarksScreenTest.kt
│   │           ├── main/
│   │           │   ├── AndroidManifest.xml
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── bookmarks/
│   │           │                                   └── impl/
│   │           │                                       ├── BookmarksScreen.kt
│   │           │                                       ├── BookmarksViewModel.kt
│   │           │                                       └── navigation/
│   │           │                                           └── BookmarksEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── bookmarks/
│   │                                               └── impl/
│   │                                                   └── BookmarksViewModelTest.kt
│   ├── foryou/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── foryou/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── ForYouNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_foryou_api_ic_icon_placeholder.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── foryou/
│   │           │                                   └── impl/
│   │           │                                       └── ForYouScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── foryou/
│   │           │                                   └── impl/
│   │           │                                       ├── ForYouScreen.kt
│   │           │                                       ├── ForYouViewModel.kt
│   │           │                                       ├── OnboardingUiState.kt
│   │           │                                       └── navigation/
│   │           │                                           └── ForYouEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── foryou/
│   │                                               └── impl/
│   │                                                   ├── ForYouScreenScreenshotTests.kt
│   │                                                   └── ForYouViewModelTest.kt
│   ├── interests/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── interests/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── InterestsNavKey.kt
│   │   │           └── res/
│   │   │               ├── drawable/
│   │   │               │   └── feature_interests_api_ic_detail_placeholder.xml
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── interests/
│   │           │                                   └── impl/
│   │           │                                       └── InterestsScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── interests/
│   │           │                                   └── impl/
│   │           │                                       ├── InterestsDetailPlaceholder.kt
│   │           │                                       ├── InterestsScreen.kt
│   │           │                                       ├── InterestsViewModel.kt
│   │           │                                       ├── TabContent.kt
│   │           │                                       └── navigation/
│   │           │                                           └── InterestsEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── interests/
│   │                                           └── impl/
│   │                                               ├── InterestsListDetailScreenTest.kt
│   │                                               └── InterestsViewModelTest.kt
│   ├── search/
│   │   ├── api/
│   │   │   ├── .gitignore
│   │   │   ├── README.md
│   │   │   ├── build.gradle.kts
│   │   │   └── src/
│   │   │       └── main/
│   │   │           ├── AndroidManifest.xml
│   │   │           ├── kotlin/
│   │   │           │   └── com/
│   │   │           │       └── google/
│   │   │           │           └── samples/
│   │   │           │               └── apps/
│   │   │           │                   └── nowinandroid/
│   │   │           │                       └── feature/
│   │   │           │                           └── search/
│   │   │           │                               └── api/
│   │   │           │                                   └── navigation/
│   │   │           │                                       └── SearchNavKey.kt
│   │   │           └── res/
│   │   │               └── values/
│   │   │                   └── strings.xml
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── search/
│   │           │                                   └── impl/
│   │           │                                       └── SearchScreenTest.kt
│   │           ├── main/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── search/
│   │           │                                   └── impl/
│   │           │                                       ├── RecentSearchQueriesUiState.kt
│   │           │                                       ├── SearchResultUiState.kt
│   │           │                                       ├── SearchScreen.kt
│   │           │                                       ├── SearchUiStatePreviewParameterProvider.kt
│   │           │                                       ├── SearchViewModel.kt
│   │           │                                       └── navigation/
│   │           │                                           └── SearchEntryProvider.kt
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── search/
│   │                                               └── impl/
│   │                                                   └── SearchViewModelTest.kt
│   ├── settings/
│   │   └── impl/
│   │       ├── .gitignore
│   │       ├── README.md
│   │       ├── build.gradle.kts
│   │       └── src/
│   │           ├── androidTest/
│   │           │   └── kotlin/
│   │           │       └── com/
│   │           │           └── google/
│   │           │               └── samples/
│   │           │                   └── apps/
│   │           │                       └── nowinandroid/
│   │           │                           └── feature/
│   │           │                               └── settings/
│   │           │                                   └── impl/
│   │           │                                       └── SettingsDialogTest.kt
│   │           ├── main/
│   │           │   ├── AndroidManifest.xml
│   │           │   ├── kotlin/
│   │           │   │   └── com/
│   │           │   │       └── google/
│   │           │   │           └── samples/
│   │           │   │               └── apps/
│   │           │   │                   └── nowinandroid/
│   │           │   │                       └── feature/
│   │           │   │                           └── settings/
│   │           │   │                               └── impl/
│   │           │   │                                   ├── SettingsDialog.kt
│   │           │   │                                   └── SettingsViewModel.kt
│   │           │   └── res/
│   │           │       └── values/
│   │           │           └── strings.xml
│   │           └── test/
│   │               └── kotlin/
│   │                   └── com/
│   │                       └── google/
│   │                           └── samples/
│   │                               └── apps/
│   │                                   └── nowinandroid/
│   │                                       └── feature/
│   │                                           └── settings/
│   │                                               └── impl/
│   │                                                   └── SettingsViewModelTest.kt
│   └── topic/
│       ├── api/
│       │   ├── .gitignore
│       │   ├── README.md
│       │   ├── build.gradle.kts
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           ├── kotlin/
│       │           │   └── com/
│       │           │       └── google/
│       │           │           └── samples/
│       │           │               └── apps/
│       │           │                   └── nowinandroid/
│       │           │                       └── feature/
│       │           │                           └── topic/
│       │           │                               └── api/
│       │           │                                   └── navigation/
│       │           │                                       └── TopicNavKey.kt
│       │           └── res/
│       │               └── values/
│       │                   └── strings.xml
│       └── impl/
│           ├── .gitignore
│           ├── README.md
│           ├── build.gradle.kts
│           └── src/
│               ├── androidTest/
│               │   └── kotlin/
│               │       └── com/
│               │           └── google/
│               │               └── samples/
│               │                   └── apps/
│               │                       └── nowinandroid/
│               │                           └── feature/
│               │                               └── topic/
│               │                                   └── impl/
│               │                                       └── TopicScreenTest.kt
│               ├── main/
│               │   └── kotlin/
│               │       └── com/
│               │           └── google/
│               │               └── samples/
│               │                   └── apps/
│               │                       └── nowinandroid/
│               │                           └── feature/
│               │                               └── topic/
│               │                                   └── impl/
│               │                                       ├── TopicScreen.kt
│               │                                       ├── TopicViewModel.kt
│               │                                       └── navigation/
│               │                                           └── TopicEntryProvider.kt
│               └── test/
│                   └── kotlin/
│                       └── com/
│                           └── google/
│                               └── samples/
│                                   └── apps/
│                                       └── nowinandroid/
│                                           └── feature/
│                                               └── topic/
│                                                   └── impl/
│                                                       └── TopicViewModelTest.kt
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── kokoro/
│   ├── build.sh
│   ├── continuous.cfg
│   ├── nightly.cfg
│   ├── nightly.sh
│   └── presubmit.cfg
├── lint/
│   ├── .gitignore
│   ├── README.md
│   ├── build.gradle.kts
│   └── src/
│       ├── main/
│       │   ├── kotlin/
│       │   │   └── com/
│       │   │       └── google/
│       │   │           └── samples/
│       │   │               └── apps/
│       │   │                   └── nowinandroid/
│       │   │                       └── lint/
│       │   │                           ├── NiaIssueRegistry.kt
│       │   │                           ├── TestMethodNameDetector.kt
│       │   │                           └── designsystem/
│       │   │                               └── DesignSystemDetector.kt
│       │   └── resources/
│       │       └── META-INF/
│       │           └── services/
│       │               └── com.android.tools.lint.client.api.IssueRegistry
│       └── test/
│           └── kotlin/
│               └── com/
│                   └── google/
│                       └── samples/
│                           └── apps/
│                               └── nowinandroid/
│                                   └── lint/
│                                       ├── TestMethodNameDetectorTest.kt
│                                       └── designsystem/
│                                           └── DesignSystemDetectorTest.kt
├── settings.gradle.kts
├── spotless/
│   ├── copyright.kt
│   ├── copyright.kts
│   └── copyright.xml
├── sync/
│   ├── sync-test/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── build.gradle.kts
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           └── kotlin/
│   │               └── com/
│   │                   └── google/
│   │                       └── samples/
│   │                           └── apps/
│   │                               └── nowinandroid/
│   │                                   └── core/
│   │                                       └── sync/
│   │                                           └── test/
│   │                                               ├── NeverSyncingSyncManager.kt
│   │                                               └── TestSyncModule.kt
│   └── work/
│       ├── .gitignore
│       ├── README.md
│       ├── build.gradle.kts
│       └── src/
│           ├── androidTest/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── sync/
│           │                               └── workers/
│           │                                   └── SyncWorkerTest.kt
│           ├── demo/
│           │   └── kotlin/
│           │       └── com/
│           │           └── google/
│           │               └── samples/
│           │                   └── apps/
│           │                       └── nowinandroid/
│           │                           └── sync/
│           │                               └── di/
│           │                                   └── SyncModule.kt
│           ├── main/
│           │   ├── kotlin/
│           │   │   └── com/
│           │   │       └── google/
│           │   │           └── samples/
│           │   │               └── apps/
│           │   │                   └── nowinandroid/
│           │   │                       └── sync/
│           │   │                           ├── initializers/
│           │   │                           │   ├── SyncInitializer.kt
│           │   │                           │   └── SyncWorkHelpers.kt
│           │   │                           ├── status/
│           │   │                           │   ├── StubSyncSubscriber.kt
│           │   │                           │   ├── SyncSubscriber.kt
│           │   │                           │   └── WorkManagerSyncManager.kt
│           │   │                           └── workers/
│           │   │                               ├── AnalyticsExtensions.kt
│           │   │                               ├── DelegatingWorker.kt
│           │   │                               └── SyncWorker.kt
│           │   └── res/
│           │       └── values/
│           │           └── strings.xml
│           └── prod/
│               ├── AndroidManifest.xml
│               └── kotlin/
│                   └── com/
│                       └── google/
│                           └── samples/
│                               └── apps/
│                                   └── nowinandroid/
│                                       └── sync/
│                                           ├── di/
│                                           │   └── SyncModule.kt
│                                           ├── services/
│                                           │   └── SyncNotificationsService.kt
│                                           └── status/
│                                               └── FirebaseSyncSubscriber.kt
├── tools/
│   ├── nowinandroid-codestyle.xml
│   ├── pre-push
│   └── setup.sh
└── ui-test-hilt-manifest/
    ├── .gitignore
    ├── README.md
    ├── build.gradle.kts
    └── src/
        └── main/
            ├── AndroidManifest.xml
            └── kotlin/
                └── com/
                    └── google/
                        └── samples/
                            └── apps/
                                └── nowinandroid/
                                    └── uitesthiltmanifest/
                                        └── HiltComponentActivity.kt
Condensed preview — 554 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,031K chars).
[
  {
    "path": ".editorconfig",
    "chars": 747,
    "preview": "# https://editorconfig.org/\n# This configuration is used by ktlint when spotless invokes it\n\n[*.{kt,kts}]\nij_kotlin_allo"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1650,
    "preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"bug\", \"triage me\"]\nbody:\n  - type: markdown\n "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/docs_issue.yml",
    "chars": 1258,
    "preview": "name: Documentation issue\ndescription: File an issue or make a suggestion for the project documentation\ntitle: \"[Documen"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1511,
    "preview": "name: Feature request\ndescription: File a feature request\ntitle: \"[FR]: \"\nlabels: [\"enhancement\", \"triage me\"]\nbody:\n  -"
  },
  {
    "path": ".github/ci-gradle.properties",
    "chars": 1028,
    "preview": "#\n# Copyright 2020 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 902,
    "preview": "**DO NOT CREATE A PULL REQUEST WITHOUT READING THESE INSTRUCTIONS**\n\n## Instructions\nThanks for submitting a pull reques"
  },
  {
    "path": ".github/renovate.json",
    "chars": 357,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"local>android/.github:renovate-c"
  },
  {
    "path": ".github/workflows/Build.yaml",
    "chars": 10241,
    "preview": "name: Build\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n  pull_request:\n\nconcurrency:\n  group: build-${"
  },
  {
    "path": ".github/workflows/NightlyBaselineProfiles.yaml",
    "chars": 2188,
    "preview": "name: NightlyBaselineProfiles\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron:  '42 4 * * *'\n\njobs:\n  baseline_profiles"
  },
  {
    "path": ".github/workflows/Release.yml",
    "chars": 2877,
    "preview": "name: GitHub Release with APKs\n\non:\n  workflow_dispatch:\n  push:\n    tags:\n    - 'v*'\n\njobs:\n  build:\n    if: github.rep"
  },
  {
    "path": ".gitignore",
    "chars": 665,
    "preview": "# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\n"
  },
  {
    "path": ".google/BUILDME",
    "chars": 84,
    "preview": "# This file can be used to trigger an internal build by changing the number below\n2\n"
  },
  {
    "path": ".google/packaging.yaml",
    "chars": 1519,
    "preview": "# Copyright (C) 2022 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 11145,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JavaCodeStyleSettings"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 142,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
  },
  {
    "path": ".idea/copyright/The_Android_Open_Source_Project.xml",
    "chars": 840,
    "preview": "<component name=\"CopyrightManager\">\n  <copyright>\n    <option name=\"notice\" value=\"Copyright &amp;#36;today.year The And"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 105,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"The Android Open Source Project\" />\n</component>"
  },
  {
    "path": ".run/Generate Demo Baseline Profile.run.xml",
    "chars": 2285,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": ".run/spotlessApply.run.xml",
    "chars": 866,
    "preview": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"spotlessApply\" type=\"GradleRunC"
  },
  {
    "path": "AGENTS.md",
    "chars": 3021,
    "preview": "# Now in Android Project\n\nNow in Android is a native Android mobile application written in Kotlin. It provides regular n"
  },
  {
    "path": "CODEOWNERS",
    "chars": 11,
    "preview": "* @dturner\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3167,
    "preview": "# Google Open Source Community Guidelines\n\nAt Google, we recognize and celebrate the creativity and collaboration of ope"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1629,
    "preview": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your samp"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 10995,
    "preview": "![Now in Android](docs/images/nia-splash.jpg \"Now in Android\")\n\n<a href=\"https://play.google.com/store/apps/details?id=c"
  },
  {
    "path": "app/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "app/README.md",
    "chars": 6132,
    "preview": "# `:app`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nodePlacementS"
  },
  {
    "path": "app/benchmark-rules.pro",
    "chars": 896,
    "preview": "# Proguard rules for the `benchmark` build type.\n#\n# Obsfuscation must be disabled for the build variant that generates "
  },
  {
    "path": "app/build.gradle.kts",
    "chars": 6168,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/dependencies/prodReleaseRuntimeClasspath.txt",
    "chars": 12365,
    "preview": "androidx.activity:activity-compose:1.12.0\nandroidx.activity:activity-ktx:1.12.0\nandroidx.activity:activity:1.12.0\nandroi"
  },
  {
    "path": "app/google-services.json",
    "chars": 2917,
    "preview": "{\n  \"project_info\": {\n    \"project_number\": \"YourProjectId\",\n    \"project_id\": \"abc\",\n    \"storage_bucket\": \"abc\"\n  },\n "
  },
  {
    "path": "app/prodRelease-badging.txt",
    "chars": 5833,
    "preview": "package: name='com.google.samples.apps.nowinandroid' versionCode='8' versionName='0.1.2' platformBuildVersionName='16' p"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 98,
    "preview": "# Repackage classes into the default package to reduce the size of descriptors.\n-repackageclasses\n"
  },
  {
    "path": "app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/NavigationTest.kt",
    "chars": 13442,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/androidTest/kotlin/com/google/samples/apps/nowinandroid/ui/UiTestExtensions.kt",
    "chars": 978,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/benchmark/res/values/colors.xml",
    "chars": 1009,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/benchmark/res/values-night/colors.xml",
    "chars": 1009,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/debug/res/values/colors.xml",
    "chars": 1005,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/debug/res/values-night/colors.xml",
    "chars": 1005,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 3322,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivity.kt",
    "chars": 8041,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/MainActivityViewModel.kt",
    "chars": 3075,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaApplication.kt",
    "chars": 2304,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/di/JankStatsModule.kt",
    "chars": 1642,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/navigation/TopLevelNavItem.kt",
    "chars": 2948,
    "preview": "/*\n * Copyright 2025 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaApp.kt",
    "chars": 13134,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppState.kt",
    "chars": 4539,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/ProfileVerifierLogger.kt",
    "chars": 2685,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/kotlin/com/google/samples/apps/nowinandroid/util/UiExtensions.kt",
    "chars": 1753,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 982,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "chars": 2183,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/drawable/ic_splash.xml",
    "chars": 2320,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 974,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 974,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 820,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 830,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "chars": 1620,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "chars": 820,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "chars": 1094,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2021 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/prod/AndroidManifest.xml",
    "chars": 1069,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/DeviceConfigurationOverrideWindowInsets.kt",
    "chars": 2685,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppScreenSizesScreenshotTests.kt",
    "chars": 7714,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/NiaAppStateTest.kt",
    "chars": 6159,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarInsetsScreenshotTests.kt",
    "chars": 12624,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/testDemo/kotlin/com/google/samples/apps/nowinandroid/ui/SnackbarScreenshotTests.kt",
    "chars": 8276,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app/src/testDemo/resources/robolectric.properties",
    "chars": 609,
    "preview": "#\n# Copyright 2025 The Android Open Source Project\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "app-nia-catalog/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "app-nia-catalog/README.md",
    "chars": 1828,
    "preview": "# `:app-nia-catalog`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    no"
  },
  {
    "path": "app-nia-catalog/build.gradle.kts",
    "chars": 2752,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app-nia-catalog/dependencies/releaseRuntimeClasspath.txt",
    "chars": 6664,
    "preview": "androidx.activity:activity-compose:1.9.3\nandroidx.activity:activity-ktx:1.9.3\nandroidx.activity:activity:1.9.3\nandroidx."
  },
  {
    "path": "app-nia-catalog/src/main/AndroidManifest.xml",
    "chars": 1505,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/kotlin/com/google/samples/apps/niacatalog/NiaCatalogActivity.kt",
    "chars": 1126,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app-nia-catalog/src/main/kotlin/com/google/samples/apps/niacatalog/ui/Catalog.kt",
    "chars": 17635,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "app-nia-catalog/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 955,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/res/drawable/ic_launcher_foreground.xml",
    "chars": 2676,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 974,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 974,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/res/values/strings.xml",
    "chars": 745,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "app-nia-catalog/src/main/res/values/themes.xml",
    "chars": 784,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "benchmarks/README.md",
    "chars": 6139,
    "preview": "# `:benchmarks`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nodePla"
  },
  {
    "path": "benchmarks/build.gradle.kts",
    "chars": 2508,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/AndroidManifest.xml",
    "chars": 683,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt",
    "chars": 1519,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/BaselineProfileMetrics.kt",
    "chars": 1876,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt",
    "chars": 2392,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Utils.kt",
    "chars": 2127,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BookmarksBaselineProfile.kt",
    "chars": 1362,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt",
    "chars": 1587,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/InterestsBaselineProfile.kt",
    "chars": 1490,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt",
    "chars": 1518,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt",
    "chars": 1120,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt",
    "chars": 4011,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt",
    "chars": 2084,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsActions.kt",
    "chars": 1881,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListBenchmark.kt",
    "chars": 2198,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt",
    "chars": 3212,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt",
    "chars": 2206,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt",
    "chars": 2886,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/README.md",
    "chars": 2063,
    "preview": "# Convention Plugins\n\nThe `build-logic` folder defines project-specific convention plugins, used to keep a single\nsource"
  },
  {
    "path": "build-logic/convention/build.gradle.kts",
    "chars": 4887,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt",
    "chars": 1265,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt",
    "chars": 2035,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt",
    "chars": 2808,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt",
    "chars": 1092,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt",
    "chars": 1409,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidFeatureApiConventionPlugin.kt",
    "chars": 1121,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidFeatureImplConventionPlugin.kt",
    "chars": 2158,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt",
    "chars": 1249,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt",
    "chars": 2990,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt",
    "chars": 1389,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt",
    "chars": 1646,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt",
    "chars": 1923,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt",
    "chars": 1330,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/HiltConventionPlugin.kt",
    "chars": 1834,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt",
    "chars": 1379,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/RootPlugin.kt",
    "chars": 1379,
    "preview": "/*\n * Copyright 2025 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt",
    "chars": 2555,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt",
    "chars": 1376,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Badging.kt",
    "chars": 5313,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt",
    "chars": 2351,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Graph.kt",
    "chars": 13150,
    "preview": "/*\n * Copyright 2025 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt",
    "chars": 5263,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt",
    "chars": 4139,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt",
    "chars": 867,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt",
    "chars": 2338,
    "preview": "/*\n * Copyright 2026 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/PrintTestApks.kt",
    "chars": 4004,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt",
    "chars": 945,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Spotless.kt",
    "chars": 2940,
    "preview": "/*\n * Copyright 2026 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build-logic/gradle.properties",
    "chars": 263,
    "preview": "# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534\norg.gradle.parallel=t"
  },
  {
    "path": "build-logic/settings.gradle.kts",
    "chars": 1191,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build.gradle.kts",
    "chars": 1938,
    "preview": "/*\n * Copyright 2021 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "build_android_release.sh",
    "chars": 1746,
    "preview": "#!/usr/bin/env bash\n\n#\n# Copyright 2022 The Android Open Source Project\n#\n#   Licensed under the Apache License, Version"
  },
  {
    "path": "compose_compiler_config.conf",
    "chars": 606,
    "preview": "// This file contains classes (with possible wildcards) that the Compose Compiler will treat as stable.\n// It allows us "
  },
  {
    "path": "core/analytics/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "core/analytics/README.md",
    "chars": 1473,
    "preview": "# `:core:analytics`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nod"
  },
  {
    "path": "core/analytics/build.gradle.kts",
    "chars": 1036,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/demo/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt",
    "chars": 996,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/main/AndroidManifest.xml",
    "chars": 684,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2023 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsEvent.kt",
    "chars": 2065,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsHelper.kt",
    "chars": 879,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/NoOpAnalyticsHelper.kt",
    "chars": 878,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/StubAnalyticsHelper.kt",
    "chars": 1164,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/main/kotlin/com/google/samples/apps/nowinandroid/core/analytics/UiHelpers.kt",
    "chars": 1117,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/AnalyticsModule.kt",
    "chars": 1337,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/analytics/src/prod/kotlin/com/google/samples/apps/nowinandroid/core/analytics/FirebaseAnalyticsHelper.kt",
    "chars": 1443,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "core/common/README.md",
    "chars": 1460,
    "preview": "# `:core:common`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nodePl"
  },
  {
    "path": "core/common/build.gradle.kts",
    "chars": 876,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/common/network/NiaDispatchers.kt",
    "chars": 913,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/common/network/di/CoroutineScopesModule.kt",
    "chars": 1546,
    "preview": "/*\n * Copyright 2026 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/common/network/di/DispatchersModule.kt",
    "chars": 1434,
    "preview": "/*\n * Copyright 2026 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/src/main/kotlin/com/google/samples/apps/nowinandroid/core/common/result/Result.kt",
    "chars": 1187,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/common/src/test/kotlin/com/google/samples/apps/nowinandroid/core/common/result/ResultKtTest.kt",
    "chars": 1684,
    "preview": "/*\n * Copyright 2026 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "core/data/README.md",
    "chars": 2327,
    "preview": "# `:core:data`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nodePlac"
  },
  {
    "path": "core/data/build.gradle.kts",
    "chars": 1397,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/AndroidManifest.xml",
    "chars": 832,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/SyncUtilities.kt",
    "chars": 4148,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/DataModule.kt",
    "chars": 3087,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/di/UserNewsResourceRepositoryModule.kt",
    "chars": 1242,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/model/NewsResource.kt",
    "chars": 2512,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/model/RecentSearchQuery.kt",
    "chars": 1056,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/model/Topic.kt",
    "chars": 1027,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/AnalyticsExtensions.kt",
    "chars": 3014,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/CompositeUserNewsResourceRepository.kt",
    "chars": 2816,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultRecentSearchRepository.kt",
    "chars": 1926,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/DefaultSearchContentsRepository.kt",
    "chars": 3966,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/NewsRepository.kt",
    "chars": 1594,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepository.kt",
    "chars": 6875,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepository.kt",
    "chars": 2837,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepository.kt",
    "chars": 3293,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/RecentSearchRepository.kt",
    "chars": 1317,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/SearchContentsRepository.kt",
    "chars": 1224,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/TopicsRepository.kt",
    "chars": 1089,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserDataRepository.kt",
    "chars": 2167,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/UserNewsResourceRepository.kt",
    "chars": 1470,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/ConnectivityManagerNetworkMonitor.kt",
    "chars": 3736,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/NetworkMonitor.kt",
    "chars": 833,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/SyncManager.kt",
    "chars": 852,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/util/TimeZoneMonitor.kt",
    "chars": 4621,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/CompositeUserNewsResourceRepositoryTest.kt",
    "chars": 7646,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/UserNewsResourceTest.kt",
    "chars": 4284,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/model/NetworkEntityTest.kt",
    "chars": 4788,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstNewsRepositoryTest.kt",
    "chars": 14583,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstTopicsRepositoryTest.kt",
    "chars": 6211,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/OfflineFirstUserDataRepositoryTest.kt",
    "chars": 8640,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/repository/TestSynchronizer.kt",
    "chars": 1377,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNewsResourceDao.kt",
    "chars": 5142,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestNiaNetworkDataSource.kt",
    "chars": 4471,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/data/testdoubles/TestTopicDao.kt",
    "chars": 2320,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data/src/test/kotlin/com/google/samples/apps/nowinandroid/core/database/model/PopulatedNewsResourceKtTest.kt",
    "chars": 2600,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "core/data-test/README.md",
    "chars": 2414,
    "preview": "# `:core:data-test`\n\n## Module dependency graph\n\n<!--region graph-->\n```mermaid\n---\nconfig:\n  layout: elk\n  elk:\n    nod"
  },
  {
    "path": "core/data-test/build.gradle.kts",
    "chars": 899,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/src/main/AndroidManifest.xml",
    "chars": 684,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n     Copyright 2022 The Android Open Source Project\n\n     Licensed under the"
  },
  {
    "path": "core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/AlwaysOnlineNetworkMonitor.kt",
    "chars": 986,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/DefaultZoneIdTimeZoneMonitor.kt",
    "chars": 1055,
    "preview": "/*\n * Copyright 2024 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/TestDataModule.kt",
    "chars": 2905,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeNewsRepository.kt",
    "chars": 3121,
    "preview": "/*\n * Copyright 2022 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "core/data-test/src/main/kotlin/com/google/samples/apps/nowinandroid/core/data/test/repository/FakeRecentSearchRepository.kt",
    "chars": 1363,
    "preview": "/*\n * Copyright 2023 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\""
  }
]

// ... and 354 more files (download for full content)

About this extraction

This page contains the full source code of the android/nowinandroid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 554 files (1.8 MB), approximately 463.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!