Merge branch 'main' into kotlinify

pull/1039/head
Don Turner 6 months ago committed by GitHub
commit 707117a4f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -65,9 +65,10 @@ android {
}
dependencies {
implementation(libs.androidx.activity.compose)
implementation(projects.core.designsystem)
implementation(projects.core.ui)
implementation(libs.androidx.activity.compose)
}
dependencyGuard {

@ -1,15 +1,16 @@
androidx.activity:activity-compose:1.8.0
androidx.activity:activity-ktx:1.8.0
androidx.activity:activity:1.8.0
androidx.annotation:annotation-experimental:1.3.0
androidx.annotation:annotation-jvm:1.6.0
androidx.annotation:annotation:1.6.0
androidx.annotation:annotation-experimental:1.3.1
androidx.annotation:annotation-jvm:1.7.0
androidx.annotation:annotation:1.7.0
androidx.appcompat:appcompat-resources:1.6.1
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.6.0
androidx.collection:collection:1.2.0
androidx.collection:collection-jvm:1.3.0
androidx.collection:collection:1.3.0
androidx.compose.animation:animation-android:1.5.4
androidx.compose.animation:animation-core-android:1.5.4
androidx.compose.animation:animation-core:1.5.4
@ -26,7 +27,6 @@ androidx.compose.material:material-icons-extended:1.5.4
androidx.compose.material:material-ripple-android:1.5.4
androidx.compose.material:material-ripple:1.5.4
androidx.compose.runtime:runtime-android:1.5.4
androidx.compose.runtime:runtime-livedata:1.5.4
androidx.compose.runtime:runtime-saveable-android:1.5.4
androidx.compose.runtime:runtime-saveable:1.5.4
androidx.compose.runtime:runtime:1.5.4
@ -50,118 +50,54 @@ androidx.core:core-ktx:1.12.0
androidx.core:core:1.12.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
androidx.datastore:datastore-core:1.0.0
androidx.datastore:datastore:1.0.0
androidx.documentfile:documentfile:1.0.0
androidx.emoji2:emoji2:1.4.0
androidx.exifinterface:exifinterface:1.3.6
androidx.fragment:fragment:1.5.1
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.6.1
androidx.lifecycle:lifecycle-common:2.6.1
androidx.lifecycle:lifecycle-livedata-core:2.6.1
androidx.lifecycle:lifecycle-livedata:2.6.1
androidx.lifecycle:lifecycle-process:2.6.1
androidx.lifecycle:lifecycle-runtime-ktx:2.6.1
androidx.lifecycle:lifecycle-runtime:2.6.1
androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1
androidx.lifecycle:lifecycle-viewmodel:2.6.1
androidx.lifecycle:lifecycle-common-java8:2.6.2
androidx.lifecycle:lifecycle-common:2.6.2
androidx.lifecycle:lifecycle-livedata-core:2.6.2
androidx.lifecycle:lifecycle-livedata:2.6.2
androidx.lifecycle:lifecycle-process:2.6.2
androidx.lifecycle:lifecycle-runtime-ktx:2.6.2
androidx.lifecycle:lifecycle-runtime:2.6.2
androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2
androidx.lifecycle:lifecycle-viewmodel:2.6.2
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-alpha04
androidx.print:print:1.0.0
androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.3.1
androidx.room:room-common:2.6.0
androidx.room:room-ktx:2.6.0
androidx.room:room-runtime:2.6.0
androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1
androidx.sqlite:sqlite-framework:2.4.0
androidx.sqlite:sqlite:2.4.0
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.1.0
androidx.tracing:tracing:1.1.0
androidx.tracing:tracing:1.0.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
com.caverock:androidsvg-aar:1.4
com.google.accompanist:accompanist-drawablepainter:0.30.1
com.google.android.datatransport:transport-api:3.0.0
com.google.android.datatransport:transport-backend-cct:3.1.8
com.google.android.datatransport:transport-runtime:3.1.8
com.google.android.gms:play-services-ads-identifier:18.0.0
com.google.android.gms:play-services-base:18.0.1
com.google.android.gms:play-services-basement:18.1.0
com.google.android.gms:play-services-cloud-messaging:17.0.1
com.google.android.gms:play-services-measurement-api:21.4.0
com.google.android.gms:play-services-measurement-base:21.4.0
com.google.android.gms:play-services-measurement-impl:21.4.0
com.google.android.gms:play-services-measurement-sdk-api:21.4.0
com.google.android.gms:play-services-measurement-sdk:21.4.0
com.google.android.gms:play-services-measurement:21.4.0
com.google.android.gms:play-services-stats:17.0.2
com.google.android.gms:play-services-tasks:18.0.2
com.google.accompanist:accompanist-drawablepainter:0.32.0
com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.48.1
com.google.dagger:dagger:2.48.1
com.google.dagger:hilt-android:2.48.1
com.google.dagger:hilt-core:2.48.1
com.google.errorprone:error_prone_annotations:2.11.0
com.google.firebase:firebase-analytics-ktx:21.4.0
com.google.firebase:firebase-analytics:21.4.0
com.google.firebase:firebase-annotations:16.2.0
com.google.firebase:firebase-bom:32.4.0
com.google.firebase:firebase-common-ktx:20.4.2
com.google.firebase:firebase-common:20.4.2
com.google.firebase:firebase-components:17.1.5
com.google.firebase:firebase-datatransport:18.1.7
com.google.firebase:firebase-encoders-json:18.0.0
com.google.firebase:firebase-encoders-proto:16.0.0
com.google.firebase:firebase-encoders:17.0.0
com.google.firebase:firebase-iid-interop:17.1.0
com.google.firebase:firebase-installations-interop:17.1.1
com.google.firebase:firebase-installations:17.2.0
com.google.firebase:firebase-measurement-connector:19.0.0
com.google.firebase:firebase-messaging-ktx:23.3.0
com.google.firebase:firebase-messaging:23.3.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:31.1-android
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.3
com.google.protobuf:protobuf-javalite:3.24.4
com.google.protobuf:protobuf-kotlin-lite:3.24.4
com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.google.dagger:dagger-lint-aar:2.50
com.google.dagger:dagger:2.50
com.google.dagger:hilt-android:2.50
com.google.dagger:hilt-core:2.50
com.google.guava:listenablefuture:1.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
com.squareup.okio:okio:3.6.0
com.squareup.retrofit2:retrofit:2.9.0
io.coil-kt:coil-base:2.4.0
io.coil-kt:coil-compose-base:2.4.0
io.coil-kt:coil-compose:2.4.0
io.coil-kt:coil-svg:2.4.0
io.coil-kt:coil:2.4.0
io.coil-kt:coil-base:2.5.0
io.coil-kt:coil-compose-base:2.5.0
io.coil-kt:coil-compose:2.5.0
io.coil-kt:coil:2.5.0
javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:1.9.21
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.1
org.jetbrains.kotlinx:kotlinx-datetime:0.4.1
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0
org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
org.jetbrains:annotations:23.0.0

@ -25,6 +25,7 @@ plugins {
alias(libs.plugins.nowinandroid.android.application.firebase)
id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.baselineprofile)
alias(libs.plugins.roborazzi)
}
android {
@ -96,47 +97,41 @@ dependencies {
implementation(projects.core.data)
implementation(projects.core.model)
implementation(projects.core.analytics)
implementation(projects.sync.work)
androidTestImplementation(projects.core.testing)
androidTestImplementation(projects.core.datastoreTest)
androidTestImplementation(projects.core.dataTest)
androidTestImplementation(projects.core.network)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.accompanist.testharness)
androidTestImplementation(kotlin("test"))
debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(projects.uiTestHiltManifest)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.window.manager)
implementation(libs.androidx.profileinstaller)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt)
baselineProfile(project(":benchmarks"))
debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(projects.uiTestHiltManifest)
kspTest(libs.hilt.compiler)
// Core functions
testImplementation(projects.core.testing)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.dataTest)
testImplementation(projects.core.network)
testImplementation(libs.androidx.navigation.testing)
testImplementation(projects.core.testing)
testImplementation(libs.accompanist.testharness)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.work.testing)
testImplementation(kotlin("test"))
kspTest(libs.hilt.compiler)
testDemoImplementation(libs.robolectric)
testDemoImplementation(libs.roborazzi)
androidTestImplementation(projects.core.testing)
androidTestImplementation(projects.core.dataTest)
androidTestImplementation(projects.core.datastoreTest)
androidTestImplementation(libs.androidx.navigation.testing)
androidTestImplementation(libs.accompanist.testharness)
androidTestImplementation(libs.hilt.android.testing)
baselineProfile(projects.benchmarks)
}
baselineProfile {

@ -1,17 +1,18 @@
androidx.activity:activity-compose:1.8.0
androidx.activity:activity-ktx:1.8.0
androidx.activity:activity:1.8.0
androidx.annotation:annotation-experimental:1.3.0
androidx.annotation:annotation-jvm:1.6.0
androidx.annotation:annotation:1.6.0
androidx.annotation:annotation-experimental:1.3.1
androidx.annotation:annotation-jvm:1.7.0
androidx.annotation:annotation:1.7.0
androidx.appcompat:appcompat-resources:1.6.1
androidx.appcompat:appcompat:1.6.1
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.6.0
androidx.collection:collection-ktx:1.1.0
androidx.collection:collection:1.2.0
androidx.collection:collection-jvm:1.3.0
androidx.collection:collection-ktx:1.3.0
androidx.collection:collection:1.3.0
androidx.compose.animation:animation-android:1.5.4
androidx.compose.animation:animation-core-android:1.5.4
androidx.compose.animation:animation-core:1.5.4
@ -29,10 +30,8 @@ androidx.compose.material:material-icons-extended:1.5.4
androidx.compose.material:material-ripple-android:1.5.4
androidx.compose.material:material-ripple:1.5.4
androidx.compose.runtime:runtime-android:1.5.4
androidx.compose.runtime:runtime-livedata:1.5.4
androidx.compose.runtime:runtime-saveable-android:1.5.4
androidx.compose.runtime:runtime-saveable:1.5.4
androidx.compose.runtime:runtime-tracing:1.0.0-alpha03
androidx.compose.runtime:runtime:1.5.4
androidx.compose.ui:ui-android:1.5.4
androidx.compose.ui:ui-geometry-android:1.5.4
@ -74,9 +73,7 @@ androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.6.2
androidx.lifecycle:lifecycle-common:2.6.2
androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.2
androidx.lifecycle:lifecycle-livedata-core:2.6.2
androidx.lifecycle:lifecycle-livedata-ktx:2.6.2
androidx.lifecycle:lifecycle-livedata:2.6.2
androidx.lifecycle:lifecycle-process:2.6.2
androidx.lifecycle:lifecycle-runtime-compose:2.6.2
@ -100,28 +97,25 @@ androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05
androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.3.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.room:room-common:2.6.0
androidx.room:room-ktx:2.6.0
androidx.room:room-runtime:2.6.0
androidx.room:room-common:2.6.1
androidx.room:room-ktx:2.6.1
androidx.room:room-runtime:2.6.1
androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1
androidx.sqlite:sqlite-framework:2.4.0
androidx.sqlite:sqlite:2.4.0
androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.2.0-alpha02
androidx.tracing:tracing-perfetto-common:1.0.0-alpha11
androidx.tracing:tracing-perfetto:1.0.0-alpha11
androidx.tracing:tracing:1.2.0-alpha02
androidx.tracing:tracing-ktx:1.1.0
androidx.tracing:tracing:1.1.0
androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
androidx.window.extensions.core:core:1.0.0
androidx.window:window:1.1.0
androidx.window:window:1.0.0
androidx.work:work-runtime-ktx:2.9.0-rc01
androidx.work:work-runtime:2.9.0-rc01
com.caverock:androidsvg-aar:1.4
com.google.accompanist:accompanist-drawablepainter:0.30.1
com.google.accompanist:accompanist-drawablepainter:0.32.0
com.google.accompanist:accompanist-permissions:0.32.0
com.google.android.datatransport:transport-api:3.0.0
com.google.android.datatransport:transport-backend-cct:3.1.9
@ -140,10 +134,10 @@ com.google.android.gms:play-services-oss-licenses:17.0.1
com.google.android.gms:play-services-stats:17.0.2
com.google.android.gms:play-services-tasks:18.0.2
com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.48.1
com.google.dagger:dagger:2.48.1
com.google.dagger:hilt-android:2.48.1
com.google.dagger:hilt-core:2.48.1
com.google.dagger:dagger-lint-aar:2.50
com.google.dagger:dagger:2.50
com.google.dagger:hilt-android:2.50
com.google.dagger:hilt-core:2.50
com.google.errorprone:error_prone_annotations:2.11.0
com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics-ktx:21.4.0
@ -182,27 +176,27 @@ com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
com.squareup.okio:okio:3.6.0
com.squareup.retrofit2:retrofit:2.9.0
io.coil-kt:coil-base:2.4.0
io.coil-kt:coil-compose-base:2.4.0
io.coil-kt:coil-compose:2.4.0
io.coil-kt:coil-svg:2.4.0
io.coil-kt:coil:2.4.0
io.coil-kt:coil-base:2.5.0
io.coil-kt:coil-compose-base:2.5.0
io.coil-kt:coil-compose:2.5.0
io.coil-kt:coil-svg:2.5.0
io.coil-kt:coil:2.5.0
io.github.aakira:napier-android:1.4.1
io.github.aakira:napier:1.4.1
javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.21
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:1.9.10
org.jetbrains.kotlin:kotlin-stdlib:1.9.21
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.1
org.jetbrains.kotlinx:kotlinx-datetime:0.4.1
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0
org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0

@ -105,9 +105,9 @@ application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Now in Android' icon='res/mipmap-anydpi-v26/ic_launcher.xml'
launchable-activity: name='com.google.samples.apps.nowinandroid.MainActivity' label='' icon=''
uses-library-not-required:'android.ext.adservices'
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
uses-library-not-required:'android.ext.adservices'
feature-group: label=''
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'

@ -51,7 +51,7 @@ import javax.inject.Inject
import kotlin.properties.ReadOnlyProperty
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.R as FeatureForyouR
import com.google.samples.apps.nowinandroid.feature.interests.R as FeatureInterestsR
import com.google.samples.apps.nowinandroid.feature.search.R as FeatureSearchR
import com.google.samples.apps.nowinandroid.feature.settings.R as SettingsR
/**
@ -93,15 +93,15 @@ class NavigationTest {
ReadOnlyProperty<Any, String> { _, _ -> activity.getString(resId) }
// The strings used for matching in these tests
private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up)
private val forYou by composeTestRule.stringResource(FeatureForyouR.string.for_you)
private val interests by composeTestRule.stringResource(FeatureInterestsR.string.interests)
private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_navigate_up)
private val forYou by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_title)
private val interests by composeTestRule.stringResource(FeatureSearchR.string.feature_search_interests)
private val sampleTopic = "Headlines"
private val appName by composeTestRule.stringResource(R.string.app_name)
private val saved by composeTestRule.stringResource(BookmarksR.string.saved)
private val settings by composeTestRule.stringResource(SettingsR.string.top_app_bar_action_icon_description)
private val brand by composeTestRule.stringResource(SettingsR.string.brand_android)
private val ok by composeTestRule.stringResource(SettingsR.string.dismiss_dialog_button_text)
private val saved by composeTestRule.stringResource(BookmarksR.string.feature_bookmarks_title)
private val settings by composeTestRule.stringResource(SettingsR.string.feature_settings_top_app_bar_action_icon_description)
private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_brand_android)
private val ok by composeTestRule.stringResource(SettingsR.string.feature_settings_dismiss_dialog_button_text)
@Before
fun setup() = hiltRule.inject()
@ -166,7 +166,10 @@ class NavigationTest {
composeTestRule.apply {
// GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown.
onNodeWithContentDescription(navigateUp).assertDoesNotExist()
// TODO: Add top level destinations here, see b/226357686.
onNodeWithText(saved).performClick()
onNodeWithContentDescription(navigateUp).assertDoesNotExist()
onNodeWithText(interests).performClick()
onNodeWithContentDescription(navigateUp).assertDoesNotExist()
}

@ -21,7 +21,7 @@ import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR
import com.google.samples.apps.nowinandroid.feature.foryou.R as forYouR
import com.google.samples.apps.nowinandroid.feature.interests.R as interestsR
import com.google.samples.apps.nowinandroid.feature.search.R as searchR
/**
* Type for the top level destinations in the application. Each of these destinations
@ -37,19 +37,19 @@ enum class TopLevelDestination(
FOR_YOU(
selectedIcon = NiaIcons.Upcoming,
unselectedIcon = NiaIcons.UpcomingBorder,
iconTextId = forYouR.string.for_you,
iconTextId = forYouR.string.feature_foryou_title,
titleTextId = R.string.app_name,
),
BOOKMARKS(
selectedIcon = NiaIcons.Bookmarks,
unselectedIcon = NiaIcons.BookmarksBorder,
iconTextId = bookmarksR.string.saved,
titleTextId = bookmarksR.string.saved,
iconTextId = bookmarksR.string.feature_bookmarks_title,
titleTextId = bookmarksR.string.feature_bookmarks_title,
),
INTERESTS(
selectedIcon = NiaIcons.Grid3x3,
unselectedIcon = NiaIcons.Grid3x3,
iconTextId = interestsR.string.interests,
titleTextId = interestsR.string.interests,
iconTextId = searchR.string.feature_search_interests,
titleTextId = searchR.string.feature_search_interests,
),
}

@ -181,11 +181,11 @@ fun NiaApp(
titleRes = destination.titleTextId,
navigationIcon = NiaIcons.Search,
navigationIconContentDescription = stringResource(
id = settingsR.string.top_app_bar_navigation_icon_description,
id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description,
),
actionIcon = NiaIcons.Settings,
actionIconContentDescription = stringResource(
id = settingsR.string.top_app_bar_action_icon_description,
id = settingsR.string.feature_settings_top_app_bar_action_icon_description,
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,

@ -115,7 +115,7 @@ class NiaAppState(
* Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the
* route.
*/
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.values().asList()
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.entries
/**
* The top level destinations that have unread news resources.

@ -45,7 +45,7 @@ class ScrollTopicListPowerMetricsBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
private val categories = PowerCategory.values()
private val categories = PowerCategory.entries
.associateWith { PowerCategoryDisplayLevel.TOTAL }
@Test

@ -41,6 +41,7 @@ dependencies {
compileOnly(libs.firebase.performance.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
compileOnly(libs.room.gradlePlugin)
implementation(libs.truth)
}

@ -24,8 +24,6 @@ class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.application")
// Screenshot Tests
pluginManager.apply("io.github.takahirom.roborazzi")
val extension = extensions.getByType<ApplicationExtension>()
configureAndroidCompose(extension)

@ -21,7 +21,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin
class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
@ -39,27 +38,12 @@ class AndroidFeatureConventionPlugin : Plugin<Project> {
}
dependencies {
add("implementation", project(":core:model"))
add("implementation", project(":core:ui"))
add("implementation", project(":core:designsystem"))
add("implementation", project(":core:data"))
add("implementation", project(":core:common"))
add("implementation", project(":core:domain"))
add("implementation", project(":core:analytics"))
add("testImplementation", kotlin("test"))
add("testImplementation", project(":core:testing"))
add("androidTestImplementation", kotlin("test"))
add("androidTestImplementation", project(":core:testing"))
add("implementation", libs.findLibrary("coil.kt").get())
add("implementation", libs.findLibrary("coil.kt.compose").get())
add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get())
add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
add("implementation", libs.findLibrary("kotlinx.coroutines.android").get())
}
}
}

@ -30,8 +30,6 @@ class AndroidHiltConventionPlugin : Plugin<Project> {
dependencies {
"implementation"(libs.findLibrary("hilt.android").get())
"ksp"(libs.findLibrary("hilt.compiler").get())
"kspAndroidTest"(libs.findLibrary("hilt.compiler").get())
"kspTest"(libs.findLibrary("hilt.compiler").get())
}
}

@ -26,8 +26,6 @@ class AndroidLibraryComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.library")
// Screenshot Tests
pluginManager.apply("io.github.takahirom.roborazzi")
val extension = extensions.getByType<LibraryExtension>()
configureAndroidCompose(extension)

@ -41,6 +41,9 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
defaultConfig.targetSdk = 34
configureFlavors(this)
configureGradleManagedDevices(this)
// The resource prefix is derived from the module name,
// so resources inside ":core:module1" must be prefixed with "core_module1_"
resourcePrefix = path.split("""\W""".toRegex()).drop(1).distinct().joinToString(separator = "_").lowercase() + "_"
}
extensions.configure<LibraryAndroidComponentsExtension> {
configurePrintApksTask(this)
@ -48,9 +51,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
}
dependencies {
add("testImplementation", kotlin("test"))
add("testImplementation", project(":core:testing"))
add("androidTestImplementation", kotlin("test"))
add("androidTestImplementation", project(":core:testing"))
}
}
}

@ -14,29 +14,25 @@
* limitations under the License.
*/
import com.google.devtools.ksp.gradle.KspExtension
import androidx.room.gradle.RoomExtension
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.process.CommandLineArgumentProvider
import java.io.File
class AndroidRoomConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp")
extensions.configure<KspExtension> {
extensions.configure<RoomExtension> {
// The schemas directory contains a schema file for each version of the Room database.
// This is required to enable Room auto migrations.
// See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration.
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
schemaDirectory("$projectDir/schemas")
}
dependencies {
@ -46,16 +42,4 @@ class AndroidRoomConventionPlugin : Plugin<Project> {
}
}
}
/**
* https://issuetracker.google.com/issues/132245929
* [Export schemas](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
*/
class RoomSchemaArgProvider(
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
val schemaDir: File,
) : CommandLineArgumentProvider {
override fun asArguments() = listOf("room.schemaLocation=${schemaDir.path}")
}
}

@ -41,11 +41,6 @@ internal fun Project.configureAndroidCompose(
val bom = libs.findLibrary("androidx-compose-bom").get()
add("implementation", platform(bom))
add("androidTestImplementation", platform(bom))
// Add ComponentActivity to debug manifest
add("debugImplementation", libs.findLibrary("androidx.compose.ui.testManifest").get())
// Screenshot Tests on JVM
add("testImplementation", libs.findLibrary("robolectric").get())
add("testImplementation", libs.findLibrary("roborazzi").get())
}
testOptions {

@ -32,6 +32,7 @@ buildscript {
// Lists all plugins used throughout the project without applying them.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.android.test) apply false
alias(libs.plugins.baselineprofile) apply false
alias(libs.plugins.kotlin.jvm) apply false
@ -44,4 +45,5 @@ plugins {
alias(libs.plugins.ksp) apply false
alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.secrets) apply false
alias(libs.plugins.room) apply false
}

@ -24,9 +24,8 @@ android {
}
dependencies {
implementation(platform(libs.firebase.bom))
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.core.ktx)
implementation(libs.firebase.analytics)
implementation(libs.kotlinx.coroutines.android)
prodImplementation(platform(libs.firebase.bom))
prodImplementation(libs.firebase.analytics)
}

@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
internal abstract class AnalyticsModule {
@Binds
abstract fun bindsAnalyticsHelper(analyticsHelperImpl: StubAnalyticsHelper): AnalyticsHelper
}

@ -27,7 +27,7 @@ private const val TAG = "StubAnalyticsHelper"
* analytics events should be sent to a backend.
*/
@Singleton
class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper {
internal class StubAnalyticsHelper @Inject constructor() : AnalyticsHelper {
override fun logEvent(event: AnalyticsEvent) {
Log.d(TAG, "Received analytics event: $event")
}

@ -28,7 +28,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
internal abstract class AnalyticsModule {
@Binds
abstract fun bindsAnalyticsHelper(analyticsHelperImpl: FirebaseAnalyticsHelper): AnalyticsHelper

@ -23,7 +23,7 @@ import javax.inject.Inject
/**
* Implementation of `AnalyticsHelper` which logs events to a Firebase backend.
*/
class FirebaseAnalyticsHelper @Inject constructor(
internal class FirebaseAnalyticsHelper @Inject constructor(
private val firebaseAnalytics: FirebaseAnalytics,
) : AnalyticsHelper {

@ -24,6 +24,6 @@ android {
}
dependencies {
implementation(libs.kotlinx.coroutines.android)
testImplementation(projects.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.turbine)
}

@ -34,7 +34,7 @@ annotation class ApplicationScope
@Module
@InstallIn(SingletonComponent::class)
object CoroutineScopesModule {
internal object CoroutineScopesModule {
@Provides
@Singleton
@ApplicationScope

@ -24,6 +24,6 @@ android {
dependencies {
api(projects.core.data)
implementation(projects.core.testing)
implementation(projects.core.common)
implementation(libs.hilt.android.testing)
}

@ -31,18 +31,16 @@ android {
}
dependencies {
api(projects.core.common)
api(projects.core.database)
api(projects.core.datastore)
api(projects.core.network)
implementation(projects.core.analytics)
implementation(projects.core.common)
implementation(projects.core.database)
implementation(projects.core.datastore)
implementation(projects.core.model)
implementation(projects.core.network)
implementation(projects.core.notifications)
implementation(libs.androidx.core.ktx)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.testing)
}

@ -35,35 +35,35 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface DataModule {
abstract class DataModule {
@Binds
fun bindsTopicRepository(
internal abstract fun bindsTopicRepository(
topicsRepository: OfflineFirstTopicsRepository,
): TopicsRepository
@Binds
fun bindsNewsResourceRepository(
internal abstract fun bindsNewsResourceRepository(
newsRepository: OfflineFirstNewsRepository,
): NewsRepository
@Binds
fun bindsUserDataRepository(
internal abstract fun bindsUserDataRepository(
userDataRepository: OfflineFirstUserDataRepository,
): UserDataRepository
@Binds
fun bindsRecentSearchRepository(
internal abstract fun bindsRecentSearchRepository(
recentSearchRepository: DefaultRecentSearchRepository,
): RecentSearchRepository
@Binds
fun bindsSearchContentsRepository(
internal abstract fun bindsSearchContentsRepository(
searchContentsRepository: DefaultSearchContentsRepository,
): SearchContentsRepository
@Binds
fun bindsNetworkMonitor(
internal abstract fun bindsNetworkMonitor(
networkMonitor: ConnectivityManagerNetworkMonitor,
): NetworkMonitor
}

@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface UserNewsResourceRepositoryModule {
internal interface UserNewsResourceRepositoryModule {
@Binds
fun bindsUserNewsResourceRepository(
userDataRepository: CompositeUserNewsResourceRepository,

@ -20,7 +20,7 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBookmarked: Boolean) {
internal fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBookmarked: Boolean) {
val eventType = if (isBookmarked) "news_resource_saved" else "news_resource_unsaved"
val paramKey = if (isBookmarked) "saved_news_resource_id" else "unsaved_news_resource_id"
logEvent(
@ -33,7 +33,7 @@ fun AnalyticsHelper.logNewsResourceBookmarkToggled(newsResourceId: String, isBoo
)
}
fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: Boolean) {
internal fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: Boolean) {
val eventType = if (isFollowed) "topic_followed" else "topic_unfollowed"
val paramKey = if (isFollowed) "followed_topic_id" else "unfollowed_topic_id"
logEvent(
@ -46,7 +46,7 @@ fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: B
)
}
fun AnalyticsHelper.logThemeChanged(themeName: String) =
internal fun AnalyticsHelper.logThemeChanged(themeName: String) =
logEvent(
AnalyticsEvent(
type = "theme_changed",
@ -56,7 +56,7 @@ fun AnalyticsHelper.logThemeChanged(themeName: String) =
),
)
fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) =
internal fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) =
logEvent(
AnalyticsEvent(
type = "dark_theme_config_changed",
@ -66,7 +66,7 @@ fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) =
),
)
fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) =
internal fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) =
logEvent(
AnalyticsEvent(
type = "dynamic_color_preference_changed",
@ -76,7 +76,7 @@ fun AnalyticsHelper.logDynamicColorPreferenceChanged(useDynamicColor: Boolean) =
),
)
fun AnalyticsHelper.logOnboardingStateChanged(shouldHideOnboarding: Boolean) {
internal fun AnalyticsHelper.logOnboardingStateChanged(shouldHideOnboarding: Boolean) {
val eventType = if (shouldHideOnboarding) "onboarding_complete" else "onboarding_reset"
logEvent(
AnalyticsEvent(type = eventType),

@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.datetime.Clock
import javax.inject.Inject
class DefaultRecentSearchRepository @Inject constructor(
internal class DefaultRecentSearchRepository @Inject constructor(
private val recentSearchQueryDao: RecentSearchQueryDao,
) : RecentSearchRepository {
override suspend fun insertOrReplaceRecentSearch(searchQuery: String) {

@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import javax.inject.Inject
class DefaultSearchContentsRepository @Inject constructor(
internal class DefaultSearchContentsRepository @Inject constructor(
private val newsResourceDao: NewsResourceDao,
private val newsResourceFtsDao: NewsResourceFtsDao,
private val topicDao: TopicDao,

@ -45,7 +45,7 @@ private const val SYNC_BATCH_SIZE = 40
* Disk storage backed implementation of the [NewsRepository].
* Reads are exclusively from local storage to support offline access.
*/
class OfflineFirstNewsRepository @Inject constructor(
internal class OfflineFirstNewsRepository @Inject constructor(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val newsResourceDao: NewsResourceDao,
private val topicDao: TopicDao,

@ -34,7 +34,7 @@ import javax.inject.Inject
* Disk storage backed implementation of the [TopicsRepository].
* Reads are exclusively from local storage to support offline access.
*/
class OfflineFirstTopicsRepository @Inject constructor(
internal class OfflineFirstTopicsRepository @Inject constructor(
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
) : TopicsRepository {

@ -25,7 +25,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.UserData
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class OfflineFirstUserDataRepository @Inject constructor(
internal class OfflineFirstUserDataRepository @Inject constructor(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
private val analyticsHelper: AnalyticsHelper,
) : UserDataRepository {

@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import javax.inject.Inject
class ConnectivityManagerNetworkMonitor @Inject constructor(
internal class ConnectivityManagerNetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context,
) : NetworkMonitor {
override val isOnline: Flow<Boolean> = callbackFlow {

@ -30,9 +30,8 @@ android {
}
dependencies {
implementation(projects.core.model)
api(projects.core.model)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
androidTestImplementation(projects.core.testing)

@ -28,7 +28,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
object DaosModule {
internal object DaosModule {
@Provides
fun providesTopicsDao(
database: NiaDatabase,

@ -28,7 +28,7 @@ import androidx.room.migration.AutoMigrationSpec
* from and Y is the schema version you're migrating to. The class should implement
* `AutoMigrationSpec`.
*/
object DatabaseMigrations {
internal object DatabaseMigrations {
@RenameColumn(
tableName = "topics",

@ -27,7 +27,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
internal object DatabaseModule {
@Provides
@Singleton
fun providesNiaDatabase(

@ -63,7 +63,7 @@ import com.google.samples.apps.nowinandroid.core.database.util.InstantConverter
@TypeConverters(
InstantConverter::class,
)
abstract class NiaDatabase : RoomDatabase() {
internal abstract class NiaDatabase : RoomDatabase() {
abstract fun topicDao(): TopicDao
abstract fun newsResourceDao(): NewsResourceDao
abstract fun topicFtsDao(): TopicFtsDao

@ -19,7 +19,7 @@ package com.google.samples.apps.nowinandroid.core.database.util
import androidx.room.TypeConverter
import kotlinx.datetime.Instant
class InstantConverter {
internal class InstantConverter {
@TypeConverter
fun longToInstant(value: Long?): Instant? =
value?.let(Instant::fromEpochMilliseconds)

@ -51,5 +51,5 @@ androidComponents.beforeVariants {
}
dependencies {
implementation(libs.protobuf.kotlin.lite)
api(libs.protobuf.kotlin.lite)
}

@ -23,10 +23,7 @@ android {
}
dependencies {
api(projects.core.datastore)
api(libs.androidx.dataStore.core)
implementation(libs.protobuf.kotlin.lite)
implementation(libs.hilt.android.testing)
implementation(projects.core.common)
implementation(projects.core.testing)
implementation(projects.core.datastore)
}

@ -35,7 +35,7 @@ import javax.inject.Singleton
components = [SingletonComponent::class],
replaces = [DataStoreModule::class],
)
object TestDataStoreModule {
internal object TestDataStoreModule {
@Provides
@Singleton

@ -33,13 +33,12 @@ android {
}
dependencies {
api(libs.androidx.dataStore.core)
api(projects.core.datastoreProto)
api(projects.core.model)
implementation(projects.core.common)
implementation(projects.core.model)
implementation(libs.androidx.dataStore.core)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.protobuf.kotlin.lite)
testImplementation(projects.core.datastoreTest)
testImplementation(projects.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
}

@ -21,7 +21,7 @@ import androidx.datastore.core.DataMigration
/**
* Migrates saved ids from [Int] to [String] types
*/
object IntToStringIdsMigration : DataMigration<UserPreferences> {
internal object IntToStringIdsMigration : DataMigration<UserPreferences> {
override suspend fun cleanUp() = Unit

@ -21,7 +21,7 @@ import androidx.datastore.core.DataMigration
/**
* Migrates from using lists to maps for user data.
*/
object ListToMapMigration : DataMigration<UserPreferences> {
internal object ListToMapMigration : DataMigration<UserPreferences> {
override suspend fun cleanUp() = Unit

@ -41,7 +41,7 @@ object DataStoreModule {
@Provides
@Singleton
fun providesUserPreferencesDataStore(
internal fun providesUserPreferencesDataStore(
@ApplicationContext context: Context,
@Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
@ApplicationScope scope: CoroutineScope,

@ -17,6 +17,7 @@ plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.roborazzi)
}
android {
@ -39,8 +40,15 @@ dependencies {
debugApi(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.core.ktx)
implementation(libs.coil.kt.compose)
testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.accompanist.testharness)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.robolectric)
testImplementation(libs.roborazzi)
testImplementation(projects.core.testing)
androidTestImplementation(libs.androidx.compose.ui.test)
androidTestImplementation(projects.core.testing)
}

@ -50,7 +50,7 @@ fun DynamicAsyncImage(
imageUrl: String,
contentDescription: String?,
modifier: Modifier = Modifier,
placeholder: Painter = painterResource(R.drawable.ic_placeholder_default),
placeholder: Painter = painterResource(R.drawable.core_designsystem_ic_placeholder_default),
) {
val iconTint = LocalTintTheme.current.iconTint
var isLoading by remember { mutableStateOf(true) }

@ -24,13 +24,10 @@ android {
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.model)
implementation(libs.hilt.android)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
api(projects.core.data)
api(projects.core.model)
ksp(libs.hilt.compiler)
implementation(libs.javax.inject)
testImplementation(projects.core.testing)
}

@ -19,5 +19,5 @@ plugins {
}
dependencies {
implementation(libs.kotlinx.datetime)
api(libs.kotlinx.datetime)
}

@ -39,16 +39,16 @@ secrets {
}
dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
api(libs.kotlinx.datetime)
api(projects.core.common)
api(projects.core.model)
implementation(libs.coil.kt)
implementation(libs.coil.kt.svg)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(libs.okhttp.logging)
implementation(libs.retrofit.core)
implementation(libs.retrofit.kotlin.serialization)
testImplementation(projects.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
}

@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface FlavoredNetworkModule {
internal interface FlavoredNetworkModule {
@Binds
fun binds(impl: FakeNiaNetworkDataSource): NiaNetworkDataSource

@ -35,7 +35,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
internal object NetworkModule {
@Provides
@Singleton

@ -71,7 +71,7 @@ private data class NetworkResponse<T>(
* [Retrofit] backed [NiaNetworkDataSource]
*/
@Singleton
class RetrofitNiaNetwork @Inject constructor(
internal class RetrofitNiaNetwork @Inject constructor(
networkJson: Json,
okhttpCallFactory: Call.Factory,
) : NiaNetworkDataSource {

@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface FlavoredNetworkModule {
internal interface FlavoredNetworkModule {
@Binds
fun binds(impl: RetrofitNiaNetwork): NiaNetworkDataSource

@ -15,7 +15,6 @@
*/
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.hilt)
}
@ -24,14 +23,10 @@ android {
}
dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
api(projects.core.model)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.androidx.browser)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.core.ktx)
implementation(projects.core.common)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.cloud.messaging)
compileOnly(platform(libs.androidx.compose.bom))
compileOnly(libs.androidx.compose.runtime)
}

@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
abstract class NotificationsModule {
internal abstract class NotificationsModule {
@Binds
abstract fun bindNotifier(
notifier: NoOpNotifier,

@ -22,6 +22,6 @@ import javax.inject.Inject
/**
* Implementation of [Notifier] which does nothing. Useful for tests and previews.
*/
class NoOpNotifier @Inject constructor() : Notifier {
internal class NoOpNotifier @Inject constructor() : Notifier {
override fun postNewsNotifications(newsResources: List<NewsResource>) = Unit
}

@ -50,7 +50,7 @@ private const val FOR_YOU_PATH = "foryou"
* Implementation of [Notifier] that displays notifications in the system tray.
*/
@Singleton
class SystemTrayNotifier @Inject constructor(
internal class SystemTrayNotifier @Inject constructor(
@ApplicationContext private val context: Context,
) : Notifier {
@ -66,7 +66,7 @@ class SystemTrayNotifier @Inject constructor(
val newsNotifications = truncatedNewsResources.map { newsResource ->
createNewsNotification {
setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
com.google.samples.apps.nowinandroid.core.common.R.drawable.core_common_ic_nia_notification,
)
.setContentTitle(newsResource.title)
.setContentText(newsResource.content)
@ -77,13 +77,13 @@ class SystemTrayNotifier @Inject constructor(
}
val summaryNotification = createNewsNotification {
val title = getString(
R.string.news_notification_group_summary,
R.string.core_notifications_news_notification_group_summary,
truncatedNewsResources.size,
)
setContentTitle(title)
.setContentText(title)
.setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
com.google.samples.apps.nowinandroid.core.common.R.drawable.core_common_ic_nia_notification,
)
// Build summary info into InboxStyle template.
.setStyle(newsNotificationStyle(truncatedNewsResources, title))
@ -140,10 +140,10 @@ private fun Context.ensureNotificationChannelExists() {
val channel = NotificationChannel(
NEWS_NOTIFICATION_CHANNEL_ID,
getString(R.string.news_notification_channel_name),
getString(R.string.core_notifications_news_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
).apply {
description = getString(R.string.news_notification_channel_description)
description = getString(R.string.core_notifications_news_notification_channel_description)
}
// Register the channel with the system
NotificationManagerCompat.from(this).createNotificationChannel(channel)

@ -15,7 +15,7 @@
limitations under the License.
-->
<resources>
<string name="news_notification_channel_name">News updates</string>
<string name="news_notification_channel_description">The latest updates on what\'s new in Android</string>
<string name="news_notification_group_summary">%1$d news updates</string>
<string name="core_notifications_news_notification_channel_name">News updates</string>
<string name="core_notifications_news_notification_channel_description">The latest updates on what\'s new in Android</string>
<string name="core_notifications_news_notification_group_summary">%1$d news updates</string>
</resources>

@ -23,7 +23,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
abstract class NotificationsModule {
internal abstract class NotificationsModule {
@Binds
abstract fun bindNotifier(
notifier: SystemTrayNotifier,

@ -24,28 +24,23 @@ android {
}
dependencies {
api(libs.accompanist.testharness)
api(libs.androidx.activity.compose)
api(kotlin("test"))
api(libs.androidx.compose.ui.test)
api(libs.androidx.test.core)
api(libs.androidx.test.espresso.core)
api(libs.androidx.test.rules)
api(libs.androidx.test.runner)
api(libs.hilt.android.testing)
api(libs.junit4)
api(libs.kotlinx.coroutines.test)
api(libs.roborazzi)
api(libs.robolectric.shadows)
api(libs.turbine)
api(projects.core.analytics)
api(projects.core.data)
api(projects.core.model)
api(projects.core.notifications)
debugApi(libs.androidx.compose.ui.testManifest)
implementation(libs.accompanist.testharness)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.test.rules)
implementation(libs.hilt.android.testing)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.kotlinx.datetime)
implementation(libs.robolectric.shadows)
implementation(projects.core.common)
implementation(projects.core.data)
implementation(projects.core.designsystem)
implementation(projects.core.domain)
implementation(projects.core.model)
implementation(projects.core.notifications)
implementation(projects.core.analytics)
implementation(libs.kotlinx.datetime)
}

@ -26,7 +26,7 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object TestDispatcherModule {
internal object TestDispatcherModule {
@Provides
@Singleton
fun providesTestDispatcher(): TestDispatcher = UnconfinedTestDispatcher()

@ -32,7 +32,7 @@ import kotlinx.coroutines.test.TestDispatcher
components = [SingletonComponent::class],
replaces = [DispatchersModule::class],
)
object TestDispatchersModule {
internal object TestDispatchersModule {
@Provides
@Dispatcher(IO)
fun providesIODispatcher(testDispatcher: TestDispatcher): CoroutineDispatcher = testDispatcher

@ -53,7 +53,7 @@ fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.c
screenshotName: String,
body: @Composable () -> Unit,
) {
DefaultTestDevices.values().forEach {
DefaultTestDevices.entries.forEach {
this.captureForDevice(it.description, it.spec, screenshotName, body = body)
}
}

@ -27,29 +27,14 @@ android {
}
dependencies {
api(libs.androidx.compose.foundation)
api(libs.androidx.compose.foundation.layout)
api(libs.androidx.compose.material.iconsExtended)
api(libs.androidx.compose.material3)
api(libs.androidx.compose.runtime)
api(libs.androidx.compose.runtime.livedata)
api(libs.androidx.compose.ui.tooling.preview)
api(libs.androidx.compose.ui.util)
api(libs.androidx.metrics)
api(libs.androidx.tracing.ktx)
api(projects.core.analytics)
api(projects.core.designsystem)
api(projects.core.model)
debugApi(libs.androidx.compose.ui.tooling)
implementation(projects.core.analytics)
implementation(projects.core.designsystem)
implementation(projects.core.domain)
implementation(projects.core.model)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.browser)
implementation(libs.androidx.core.ktx)
implementation(libs.coil.kt)
implementation(libs.coil.kt.compose)
implementation(libs.kotlinx.datetime)
androidTestImplementation(projects.core.testing)
}

@ -52,7 +52,7 @@ class NewsResourceCardTest {
composeTestRule
.onNodeWithText(
composeTestRule.activity.getString(
R.string.card_meta_data_text,
R.string.core_ui_card_meta_data_text,
dateFormatted,
newsWithKnownResourceType.type,
),
@ -123,7 +123,7 @@ class NewsResourceCardTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.getString(
R.string.unread_resource_dot_content_description,
R.string.core_ui_unread_resource_dot_content_description,
),
)
.assertIsDisplayed()
@ -147,7 +147,7 @@ class NewsResourceCardTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.getString(
R.string.unread_resource_dot_content_description,
R.string.core_ui_unread_resource_dot_content_description,
),
)
.assertDoesNotExist()

@ -91,7 +91,7 @@ fun NewsResourceCardExpanded(
onTopicClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val clickActionLabel = stringResource(R.string.card_tap_action)
val clickActionLabel = stringResource(R.string.core_ui_card_tap_action)
Card(
onClick = onClick,
shape = RoundedCornerShape(16.dp),
@ -183,7 +183,7 @@ fun NewsResourceHeaderImage(
painter = if (isError.not() && !isLocalInspection) {
imageLoader
} else {
painterResource(drawable.ic_placeholder_default)
painterResource(drawable.core_designsystem_ic_placeholder_default)
},
// TODO b/226661685: Investigate using alt text of image to populate content description
// decorative image,
@ -213,13 +213,13 @@ fun BookmarkButton(
icon = {
Icon(
imageVector = NiaIcons.BookmarkBorder,
contentDescription = stringResource(R.string.bookmark),
contentDescription = stringResource(R.string.core_ui_bookmark),
)
},
checkedIcon = {
Icon(
imageVector = NiaIcons.Bookmark,
contentDescription = stringResource(R.string.unbookmark),
contentDescription = stringResource(R.string.core_ui_unbookmark),
)
},
)
@ -230,7 +230,7 @@ fun NotificationDot(
color: Color,
modifier: Modifier = Modifier,
) {
val description = stringResource(R.string.unread_resource_dot_content_description)
val description = stringResource(R.string.core_ui_unread_resource_dot_content_description)
Canvas(
modifier = modifier
.semantics { contentDescription = description },
@ -274,7 +274,7 @@ fun NewsResourceMetaData(
val formattedDate = dateFormatted(publishDate)
Text(
if (resourceType.isNotBlank()) {
stringResource(R.string.card_meta_data_text, formattedDate, resourceType)
stringResource(R.string.core_ui_card_meta_data_text, formattedDate, resourceType)
} else {
formattedDate
},
@ -307,12 +307,12 @@ fun NewsResourceTopics(
text = {
val contentDescription = if (followableTopic.isFollowed) {
stringResource(
R.string.topic_chip_content_description_when_followed,
R.string.core_ui_topic_chip_content_description_when_followed,
followableTopic.topic.name,
)
} else {
stringResource(
R.string.topic_chip_content_description_when_not_followed,
R.string.core_ui_topic_chip_content_description_when_not_followed,
followableTopic.topic.name,
)
}

@ -15,15 +15,15 @@
limitations under the License.
-->
<resources>
<string name="bookmark">Bookmark</string>
<string name="unbookmark">Unbookmark</string>
<string name="back">Back</string>
<string name="core_ui_bookmark">Bookmark</string>
<string name="core_ui_unbookmark">Unbookmark</string>
<string name="core_ui_back">Back</string>
<string name="unread_resource_dot_content_description">Unread</string>
<string name="core_ui_unread_resource_dot_content_description">Unread</string>
<string name="card_tap_action">Open Resource Link</string>
<string name="card_meta_data_text">%1$s • %2$s</string>
<string name="core_ui_card_tap_action">Open Resource Link</string>
<string name="core_ui_card_meta_data_text">%1$s • %2$s</string>
<string name="topic_chip_content_description_when_followed">%1$s is followed</string>
<string name="topic_chip_content_description_when_not_followed">%1$s is not followed</string>
<string name="core_ui_topic_chip_content_description_when_followed">%1$s is followed</string>
<string name="core_ui_topic_chip_content_description_when_not_followed">%1$s is not followed</string>
</resources>

@ -25,5 +25,9 @@ android {
}
dependencies {
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(projects.core.data)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}

@ -59,7 +59,7 @@ class BookmarksScreenTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.saved_loading),
composeTestRule.activity.resources.getString(R.string.feature_bookmarks_loading),
)
.assertExists()
}
@ -125,7 +125,7 @@ class BookmarksScreenTest {
composeTestRule
.onAllNodesWithContentDescription(
composeTestRule.activity.getString(
com.google.samples.apps.nowinandroid.core.ui.R.string.unbookmark,
com.google.samples.apps.nowinandroid.core.ui.R.string.core_ui_unbookmark,
),
).filter(
hasAnyAncestor(
@ -156,13 +156,13 @@ class BookmarksScreenTest {
composeTestRule
.onNodeWithText(
composeTestRule.activity.getString(R.string.bookmarks_empty_error),
composeTestRule.activity.getString(R.string.feature_bookmarks_empty_error),
)
.assertExists()
composeTestRule
.onNodeWithText(
composeTestRule.activity.getString(R.string.bookmarks_empty_description),
composeTestRule.activity.getString(R.string.feature_bookmarks_empty_description),
)
.assertExists()
}

@ -114,8 +114,8 @@ internal fun BookmarksScreen(
undoBookmarkRemoval: () -> Unit = {},
clearUndoState: () -> Unit = {},
) {
val bookmarkRemovedMessage = stringResource(id = R.string.bookmark_removed)
val undoText = stringResource(id = R.string.undo)
val bookmarkRemovedMessage = stringResource(id = R.string.feature_bookmarks_removed)
val undoText = stringResource(id = R.string.feature_bookmarks_undo)
LaunchedEffect(shouldDisplayUndoBookmark) {
if (shouldDisplayUndoBookmark) {
@ -164,7 +164,7 @@ private fun LoadingState(modifier: Modifier = Modifier) {
.fillMaxWidth()
.wrapContentSize()
.testTag("forYou:loading"),
contentDesc = stringResource(id = R.string.saved_loading),
contentDesc = stringResource(id = R.string.feature_bookmarks_loading),
)
}
@ -237,7 +237,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
val iconTint = LocalTintTheme.current.iconTint
Image(
modifier = Modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.img_empty_bookmarks),
painter = painterResource(id = R.drawable.feature_bookmarks_img_empty_bookmarks),
colorFilter = if (iconTint != Color.Unspecified) ColorFilter.tint(iconTint) else null,
contentDescription = null,
)
@ -245,7 +245,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(48.dp))
Text(
text = stringResource(id = R.string.bookmarks_empty_error),
text = stringResource(id = R.string.feature_bookmarks_empty_error),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleMedium,
@ -255,7 +255,7 @@ private fun EmptyState(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(id = R.string.bookmarks_empty_description),
text = stringResource(id = R.string.feature_bookmarks_empty_description),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,

@ -15,10 +15,10 @@
limitations under the License.
-->
<resources>
<string name="saved">Saved</string>
<string name="saved_loading">Loading saved…</string>
<string name="bookmarks_empty_error">No saved updates</string>
<string name="bookmarks_empty_description">Updates you save will be stored here\nto read later</string>
<string name="bookmark_removed">Bookmark removed</string>
<string name="undo">UNDO</string>
<string name="feature_bookmarks_title">Saved</string>
<string name="feature_bookmarks_loading">Loading saved…</string>
<string name="feature_bookmarks_empty_error">No saved updates</string>
<string name="feature_bookmarks_empty_description">Updates you save will be stored here\nto read later</string>
<string name="feature_bookmarks_removed">Bookmark removed</string>
<string name="feature_bookmarks_undo">UNDO</string>
</resources>

@ -25,7 +25,13 @@ android {
}
dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.activity.compose)
implementation(libs.accompanist.permissions)
implementation(projects.core.data)
implementation(projects.core.domain)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.robolectric)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}

@ -45,7 +45,7 @@ class ForYouScreenTest {
private val doneButtonMatcher by lazy {
hasText(
composeTestRule.activity.resources.getString(R.string.done),
composeTestRule.activity.resources.getString(R.string.feature_foryou_done),
)
}
@ -70,7 +70,7 @@ class ForYouScreenTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading),
composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
)
.assertExists()
}
@ -96,7 +96,7 @@ class ForYouScreenTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading),
composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
)
.assertExists()
}
@ -215,7 +215,7 @@ class ForYouScreenTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading),
composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
)
.assertExists()
}
@ -241,7 +241,7 @@ class ForYouScreenTest {
composeTestRule
.onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading),
composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
)
.assertExists()
}

@ -217,7 +217,7 @@ internal fun ForYouScreen(
targetOffsetY = { fullHeight -> -fullHeight },
) + fadeOut(),
) {
val loadingContentDescription = stringResource(id = R.string.for_you_loading)
val loadingContentDescription = stringResource(id = R.string.feature_foryou_loading)
Box(
modifier = Modifier
.fillMaxWidth()
@ -272,7 +272,7 @@ private fun LazyStaggeredGridScope.onboarding(
item(span = StaggeredGridItemSpan.FullLine, contentType = "onboarding") {
Column(modifier = interestsItemModifier) {
Text(
text = stringResource(R.string.onboarding_guidance_title),
text = stringResource(R.string.feature_foryou_onboarding_guidance_title),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
@ -280,7 +280,7 @@ private fun LazyStaggeredGridScope.onboarding(
style = MaterialTheme.typography.titleMedium,
)
Text(
text = stringResource(R.string.onboarding_guidance_subtitle),
text = stringResource(R.string.feature_foryou_onboarding_guidance_subtitle),
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 24.dp, end = 24.dp),
@ -306,7 +306,7 @@ private fun LazyStaggeredGridScope.onboarding(
.fillMaxWidth(),
) {
Text(
text = stringResource(R.string.done),
text = stringResource(R.string.feature_foryou_done),
)
}
}
@ -435,7 +435,7 @@ fun TopicIcon(
modifier: Modifier = Modifier,
) {
DynamicAsyncImage(
placeholder = painterResource(R.drawable.ic_icon_placeholder),
placeholder = painterResource(R.drawable.feature_foryou_ic_icon_placeholder),
imageUrl = imageUrl,
// decorative
contentDescription = null,

@ -15,11 +15,11 @@
limitations under the License.
-->
<resources>
<string name="for_you">For you</string>
<string name="done">Done</string>
<string name="for_you_loading">Loading for you…</string>
<string name="navigate_up">Navigate up</string>
<string name="onboarding_guidance_title">What are you interested in?</string>
<string name="onboarding_guidance_subtitle">Updates from topics you follow will appear here. Follow some things to get started.</string>
<string name="feature_foryou_title">For you</string>
<string name="feature_foryou_done">Done</string>
<string name="feature_foryou_loading">Loading for you…</string>
<string name="feature_foryou_navigate_up">Navigate up</string>
<string name="feature_foryou_onboarding_guidance_title">What are you interested in?</string>
<string name="feature_foryou_onboarding_guidance_subtitle">Updates from topics you follow will appear here. Follow some things to get started.</string>
</resources>

@ -22,3 +22,12 @@ plugins {
android {
namespace = "com.google.samples.apps.nowinandroid.feature.interests"
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.domain)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}

@ -50,12 +50,12 @@ class InterestsScreenTest {
@Before
fun setup() {
composeTestRule.activity.apply {
interestsLoading = getString(R.string.loading)
interestsEmptyHeader = getString(R.string.empty_header)
interestsLoading = getString(R.string.feature_interests_loading)
interestsEmptyHeader = getString(R.string.feature_interests_empty_header)
interestsTopicCardFollowButton =
getString(R.string.card_follow_button_content_desc)
getString(R.string.feature_interests_card_follow_button_content_desc)
interestsTopicCardUnfollowButton =
getString(R.string.card_unfollow_button_content_desc)
getString(R.string.feature_interests_card_unfollow_button_content_desc)
}
}

@ -68,7 +68,7 @@ fun InterestsItem(
Icon(
imageVector = NiaIcons.Add,
contentDescription = stringResource(
id = string.card_follow_button_content_desc,
id = string.feature_interests_card_follow_button_content_desc,
),
)
},
@ -76,7 +76,7 @@ fun InterestsItem(
Icon(
imageVector = NiaIcons.Check,
contentDescription = stringResource(
id = string.card_unfollow_button_content_desc,
id = string.feature_interests_card_unfollow_button_content_desc,
),
)
},

@ -65,7 +65,7 @@ internal fun InterestsScreen(
InterestsUiState.Loading ->
NiaLoadingWheel(
modifier = modifier,
contentDesc = stringResource(id = R.string.loading),
contentDesc = stringResource(id = R.string.feature_interests_loading),
)
is InterestsUiState.Interests ->
TopicsTabContent(
@ -82,7 +82,7 @@ internal fun InterestsScreen(
@Composable
private fun InterestsEmptyScreen() {
Text(text = stringResource(id = R.string.empty_header))
Text(text = stringResource(id = R.string.feature_interests_empty_header))
}
@DevicePreviews

@ -15,9 +15,9 @@
limitations under the License.
-->
<resources>
<string name="interests">Interests</string>
<string name="loading">Loading data</string>
<string name="empty_header">"No available data"</string>
<string name="card_follow_button_content_desc">Follow interest</string>
<string name="card_unfollow_button_content_desc">Unfollow interest</string>
<string name="feature_interests_title">Interests</string>
<string name="feature_interests_loading">Loading data</string>
<string name="feature_interests_empty_header">"No available data"</string>
<string name="feature_interests_card_follow_button_content_desc">Follow interest</string>
<string name="feature_interests_card_unfollow_button_content_desc">Unfollow interest</string>
</resources>

@ -25,9 +25,14 @@ android {
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.domain)
implementation(projects.feature.bookmarks)
implementation(projects.feature.foryou)
implementation(projects.feature.interests)
implementation(libs.kotlinx.datetime)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}

@ -70,17 +70,17 @@ class SearchScreenTest {
@Before
fun setup() {
composeTestRule.activity.apply {
clearSearchContentDesc = getString(R.string.clear_search_text_content_desc)
clearRecentSearchesContentDesc = getString(R.string.clear_recent_searches_content_desc)
clearSearchContentDesc = getString(R.string.feature_search_clear_search_text_content_desc)
clearRecentSearchesContentDesc = getString(R.string.feature_search_clear_recent_searches_content_desc)
followButtonContentDesc =
getString(interestsR.string.card_follow_button_content_desc)
getString(interestsR.string.feature_interests_card_follow_button_content_desc)
unfollowButtonContentDesc =
getString(interestsR.string.card_unfollow_button_content_desc)
topicsString = getString(R.string.topics)
updatesString = getString(R.string.updates)
tryAnotherSearchString = getString(R.string.try_another_search) +
" " + getString(R.string.interests) + " " + getString(R.string.to_browse_topics)
searchNotReadyString = getString(R.string.search_not_ready)
getString(interestsR.string.feature_interests_card_unfollow_button_content_desc)
topicsString = getString(R.string.feature_search_topics)
updatesString = getString(R.string.feature_search_updates)
tryAnotherSearchString = getString(R.string.feature_search_try_another_search) +
" " + getString(R.string.feature_search_interests) + " " + getString(R.string.feature_search_to_browse_topics)
searchNotReadyString = getString(R.string.feature_search_not_ready)
}
}

@ -217,7 +217,7 @@ fun EmptySearchResultBody(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 48.dp),
) {
val message = stringResource(id = searchR.string.search_result_not_found, searchQuery)
val message = stringResource(id = searchR.string.feature_search_result_not_found, searchQuery)
val start = message.indexOf(searchQuery)
Text(
text = AnnotatedString(
@ -234,9 +234,9 @@ fun EmptySearchResultBody(
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 24.dp),
)
val interests = stringResource(id = searchR.string.interests)
val interests = stringResource(id = searchR.string.feature_search_interests)
val tryAnotherSearchString = buildAnnotatedString {
append(stringResource(id = searchR.string.try_another_search))
append(stringResource(id = searchR.string.feature_search_try_another_search))
append(" ")
withStyle(
style = SpanStyle(
@ -248,7 +248,7 @@ fun EmptySearchResultBody(
append(interests)
}
append(" ")
append(stringResource(id = searchR.string.to_browse_topics))
append(stringResource(id = searchR.string.feature_search_to_browse_topics))
}
ClickableText(
text = tryAnotherSearchString,
@ -276,7 +276,7 @@ private fun SearchNotReadyBody() {
modifier = Modifier.padding(horizontal = 48.dp),
) {
Text(
text = stringResource(id = searchR.string.search_not_ready),
text = stringResource(id = searchR.string.feature_search_not_ready),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 24.dp),
@ -317,7 +317,7 @@ private fun SearchResultBody(
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.topics))
append(stringResource(id = searchR.string.feature_search_topics))
}
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
@ -353,7 +353,7 @@ private fun SearchResultBody(
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.updates))
append(stringResource(id = searchR.string.feature_search_updates))
}
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
@ -405,7 +405,7 @@ private fun RecentSearchesBody(
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(stringResource(id = searchR.string.recent_searches))
append(stringResource(id = searchR.string.feature_search_recent_searches))
}
},
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
@ -420,7 +420,7 @@ private fun RecentSearchesBody(
Icon(
imageVector = NiaIcons.Close,
contentDescription = stringResource(
id = searchR.string.clear_recent_searches_content_desc,
id = searchR.string.feature_search_clear_recent_searches_content_desc,
),
tint = MaterialTheme.colorScheme.onSurface,
)
@ -458,7 +458,7 @@ private fun SearchToolbar(
Icon(
imageVector = NiaIcons.ArrowBack,
contentDescription = stringResource(
id = string.back,
id = string.core_ui_back,
),
)
}
@ -495,7 +495,7 @@ private fun SearchTextField(
Icon(
imageVector = NiaIcons.Search,
contentDescription = stringResource(
id = searchR.string.search,
id = searchR.string.feature_search_title,
),
tint = MaterialTheme.colorScheme.onSurface,
)
@ -510,7 +510,7 @@ private fun SearchTextField(
Icon(
imageVector = NiaIcons.Close,
contentDescription = stringResource(
id = searchR.string.clear_search_text_content_desc,
id = searchR.string.feature_search_clear_search_text_content_desc,
),
tint = MaterialTheme.colorScheme.onSurface,
)

@ -15,15 +15,15 @@
limitations under the License.
-->
<resources>
<string name="search">Search</string>
<string name="clear_search_text_content_desc">Clear search text</string>
<string name="search_result_not_found">Sorry, there is no content found for your search \"%1$s\"</string>
<string name="search_not_ready">Sorry, we are still processing the search index. Please come back later.</string>
<string name="try_another_search">Try another search or explorer </string>
<string name="interests">Interests</string>
<string name="to_browse_topics"> to browse topics</string>
<string name="topics">Topics</string>
<string name="updates">Updates</string>
<string name="recent_searches">Recent searches</string>
<string name="clear_recent_searches_content_desc">Clear searches</string>
<string name="feature_search_title">Search</string>
<string name="feature_search_clear_search_text_content_desc">Clear search text</string>
<string name="feature_search_result_not_found">Sorry, there is no content found for your search \"%1$s\"</string>
<string name="feature_search_not_ready">Sorry, we are still processing the search index. Please come back later</string>
<string name="feature_search_try_another_search">Try another search or explorer </string>
<string name="feature_search_interests">Interests</string>
<string name="feature_search_to_browse_topics"> to browse topics</string>
<string name="feature_search_topics">Topics</string>
<string name="feature_search_updates">Updates</string>
<string name="feature_search_recent_searches">Recent searches</string>
<string name="feature_search_clear_recent_searches_content_desc">Clear searches</string>
</resources>

@ -26,7 +26,10 @@ android {
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.google.oss.licenses) {
exclude(group = "androidx.appcompat")
}
implementation(libs.google.oss.licenses)
implementation(projects.core.data)
testImplementation(projects.core.testing)
androidTestImplementation(projects.core.testing)
}

@ -48,7 +48,7 @@ class SettingsDialogTest {
}
composeTestRule
.onNodeWithText(getString(R.string.loading))
.onNodeWithText(getString(R.string.feature_settings_loading))
.assertExists()
}
@ -71,17 +71,17 @@ class SettingsDialogTest {
}
// Check that all the possible settings are displayed.
composeTestRule.onNodeWithText(getString(R.string.brand_default)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_default)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_android)).assertExists()
composeTestRule.onNodeWithText(
getString(R.string.dark_mode_config_system_default),
getString(R.string.feature_settings_dark_mode_config_system_default),
).assertExists()
composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_light)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_light)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_dark)).assertExists()
// Check that the correct settings are selected.
composeTestRule.onNodeWithText(getString(R.string.brand_android)).assertIsSelected()
composeTestRule.onNodeWithText(getString(R.string.dark_mode_config_dark)).assertIsSelected()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_android)).assertIsSelected()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dark_mode_config_dark)).assertIsSelected()
}
@Test
@ -103,12 +103,12 @@ class SettingsDialogTest {
)
}
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertExists()
// Check that the correct default dynamic color setting is selected.
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertIsSelected()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertIsSelected()
}
@Test
@ -129,10 +129,10 @@ class SettingsDialogTest {
)
}
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference))
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference))
.assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertDoesNotExist()
}
@Test
@ -153,10 +153,10 @@ class SettingsDialogTest {
)
}
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_preference))
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_preference))
.assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_yes)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.dynamic_color_no)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_yes)).assertDoesNotExist()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_dynamic_color_no)).assertDoesNotExist()
}
@Test
@ -177,9 +177,9 @@ class SettingsDialogTest {
)
}
composeTestRule.onNodeWithText(getString(R.string.privacy_policy)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.licenses)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.brand_guidelines)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feedback)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_privacy_policy)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_licenses)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_brand_guidelines)).assertExists()
composeTestRule.onNodeWithText(getString(R.string.feature_settings_feedback)).assertExists()
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save