Merge branch 'main' into kotlinify

pull/1837/head
Don Turner 2 years ago committed by GitHub
commit 4843216871

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

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

@ -25,6 +25,7 @@ plugins {
alias(libs.plugins.nowinandroid.android.application.firebase) alias(libs.plugins.nowinandroid.android.application.firebase)
id("com.google.android.gms.oss-licenses-plugin") id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.baselineprofile) alias(libs.plugins.baselineprofile)
alias(libs.plugins.roborazzi)
} }
android { android {
@ -96,47 +97,41 @@ dependencies {
implementation(projects.core.data) implementation(projects.core.data)
implementation(projects.core.model) implementation(projects.core.model)
implementation(projects.core.analytics) implementation(projects.core.analytics)
implementation(projects.sync.work) 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.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.compose.runtime) implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.lifecycle.runtimeCompose) implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.compose.material3.windowSizeClass) implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.window.manager)
implementation(libs.androidx.profileinstaller) implementation(libs.androidx.profileinstaller)
implementation(libs.kotlinx.coroutines.guava) implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt) 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.dataTest)
testImplementation(projects.core.network) testImplementation(projects.core.testing)
testImplementation(libs.androidx.navigation.testing)
testImplementation(libs.accompanist.testharness) testImplementation(libs.accompanist.testharness)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.work.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 { baselineProfile {

@ -1,17 +1,18 @@
androidx.activity:activity-compose:1.8.0 androidx.activity:activity-compose:1.8.0
androidx.activity:activity-ktx:1.8.0 androidx.activity:activity-ktx:1.8.0
androidx.activity:activity:1.8.0 androidx.activity:activity:1.8.0
androidx.annotation:annotation-experimental:1.3.0 androidx.annotation:annotation-experimental:1.3.1
androidx.annotation:annotation-jvm:1.6.0 androidx.annotation:annotation-jvm:1.7.0
androidx.annotation:annotation:1.6.0 androidx.annotation:annotation:1.7.0
androidx.appcompat:appcompat-resources:1.6.1 androidx.appcompat:appcompat-resources:1.6.1
androidx.appcompat:appcompat:1.6.1 androidx.appcompat:appcompat:1.6.1
androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0 androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0 androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.6.0 androidx.browser:browser:1.6.0
androidx.collection:collection-ktx:1.1.0 androidx.collection:collection-jvm:1.3.0
androidx.collection:collection:1.2.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-android:1.5.4
androidx.compose.animation:animation-core-android:1.5.4 androidx.compose.animation:animation-core-android:1.5.4
androidx.compose.animation:animation-core: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-android:1.5.4
androidx.compose.material:material-ripple:1.5.4 androidx.compose.material:material-ripple:1.5.4
androidx.compose.runtime:runtime-android: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-android:1.5.4
androidx.compose.runtime:runtime-saveable: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.runtime:runtime:1.5.4
androidx.compose.ui:ui-android:1.5.4 androidx.compose.ui:ui-android:1.5.4
androidx.compose.ui:ui-geometry-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.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.6.2 androidx.lifecycle:lifecycle-common-java8:2.6.2
androidx.lifecycle:lifecycle-common: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-core:2.6.2
androidx.lifecycle:lifecycle-livedata-ktx:2.6.2
androidx.lifecycle:lifecycle-livedata:2.6.2 androidx.lifecycle:lifecycle-livedata:2.6.2
androidx.lifecycle:lifecycle-process:2.6.2 androidx.lifecycle:lifecycle-process:2.6.2
androidx.lifecycle:lifecycle-runtime-compose: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.privacysandbox.ads:ads-adservices:1.0.0-beta05
androidx.profileinstaller:profileinstaller:1.3.1 androidx.profileinstaller:profileinstaller:1.3.1
androidx.resourceinspection:resourceinspection-annotation:1.0.1 androidx.resourceinspection:resourceinspection-annotation:1.0.1
androidx.room:room-common:2.6.0 androidx.room:room-common:2.6.1
androidx.room:room-ktx:2.6.0 androidx.room:room-ktx:2.6.1
androidx.room:room-runtime:2.6.0 androidx.room:room-runtime:2.6.1
androidx.savedstate:savedstate-ktx:1.2.1 androidx.savedstate:savedstate-ktx:1.2.1
androidx.savedstate:savedstate:1.2.1 androidx.savedstate:savedstate:1.2.1
androidx.sqlite:sqlite-framework:2.4.0 androidx.sqlite:sqlite-framework:2.4.0
androidx.sqlite:sqlite:2.4.0 androidx.sqlite:sqlite:2.4.0
androidx.startup:startup-runtime:1.1.1 androidx.startup:startup-runtime:1.1.1
androidx.tracing:tracing-ktx:1.2.0-alpha02 androidx.tracing:tracing-ktx:1.1.0
androidx.tracing:tracing-perfetto-common:1.0.0-alpha11 androidx.tracing:tracing:1.1.0
androidx.tracing:tracing-perfetto:1.0.0-alpha11
androidx.tracing:tracing:1.2.0-alpha02
androidx.vectordrawable:vectordrawable-animated:1.1.0 androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0 androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1 androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0 androidx.viewpager:viewpager:1.0.0
androidx.window.extensions.core:core:1.0.0 androidx.window:window:1.0.0
androidx.window:window:1.1.0
androidx.work:work-runtime-ktx:2.9.0-rc01 androidx.work:work-runtime-ktx:2.9.0-rc01
androidx.work:work-runtime:2.9.0-rc01 androidx.work:work-runtime:2.9.0-rc01
com.caverock:androidsvg-aar:1.4 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.accompanist:accompanist-permissions:0.32.0
com.google.android.datatransport:transport-api:3.0.0 com.google.android.datatransport:transport-api:3.0.0
com.google.android.datatransport:transport-backend-cct:3.1.9 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-stats:17.0.2
com.google.android.gms:play-services-tasks:18.0.2 com.google.android.gms:play-services-tasks:18.0.2
com.google.code.findbugs:jsr305:3.0.2 com.google.code.findbugs:jsr305:3.0.2
com.google.dagger:dagger-lint-aar:2.48.1 com.google.dagger:dagger-lint-aar:2.50
com.google.dagger:dagger:2.48.1 com.google.dagger:dagger:2.50
com.google.dagger:hilt-android:2.48.1 com.google.dagger:hilt-android:2.50
com.google.dagger:hilt-core:2.48.1 com.google.dagger:hilt-core:2.50
com.google.errorprone:error_prone_annotations:2.11.0 com.google.errorprone:error_prone_annotations:2.11.0
com.google.firebase:firebase-abt:21.1.1 com.google.firebase:firebase-abt:21.1.1
com.google.firebase:firebase-analytics-ktx:21.4.0 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-jvm:3.6.0
com.squareup.okio:okio:3.6.0 com.squareup.okio:okio:3.6.0
com.squareup.retrofit2:retrofit:2.9.0 com.squareup.retrofit2:retrofit:2.9.0
io.coil-kt:coil-base:2.4.0 io.coil-kt:coil-base:2.5.0
io.coil-kt:coil-compose-base:2.4.0 io.coil-kt:coil-compose-base:2.5.0
io.coil-kt:coil-compose:2.4.0 io.coil-kt:coil-compose:2.5.0
io.coil-kt:coil-svg:2.4.0 io.coil-kt:coil-svg:2.5.0
io.coil-kt:coil:2.4.0 io.coil-kt:coil:2.5.0
io.github.aakira:napier-android:1.4.1 io.github.aakira:napier-android:1.4.1
io.github.aakira:napier:1.4.1 io.github.aakira:napier:1.4.1
javax.inject:javax.inject:1 javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0 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-jdk7:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8: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-android:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-bom: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-jvm:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core: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-guava:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-play-services: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-jvm:0.5.0
org.jetbrains.kotlinx:kotlinx-datetime:0.4.1 org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.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-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core: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-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Now in Android' icon='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='' 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.extensions'
uses-library-not-required:'androidx.window.sidecar' uses-library-not-required:'androidx.window.sidecar'
uses-library-not-required:'android.ext.adservices'
feature-group: label='' feature-group: label=''
uses-feature: name='android.hardware.faketouch' uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' 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 kotlin.properties.ReadOnlyProperty
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as BookmarksR 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.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 import com.google.samples.apps.nowinandroid.feature.settings.R as SettingsR
/** /**
@ -93,15 +93,15 @@ class NavigationTest {
ReadOnlyProperty<Any, String> { _, _ -> activity.getString(resId) } ReadOnlyProperty<Any, String> { _, _ -> activity.getString(resId) }
// The strings used for matching in these tests // The strings used for matching in these tests
private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.navigate_up) private val navigateUp by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_navigate_up)
private val forYou by composeTestRule.stringResource(FeatureForyouR.string.for_you) private val forYou by composeTestRule.stringResource(FeatureForyouR.string.feature_foryou_title)
private val interests by composeTestRule.stringResource(FeatureInterestsR.string.interests) private val interests by composeTestRule.stringResource(FeatureSearchR.string.feature_search_interests)
private val sampleTopic = "Headlines" private val sampleTopic = "Headlines"
private val appName by composeTestRule.stringResource(R.string.app_name) private val appName by composeTestRule.stringResource(R.string.app_name)
private val saved by composeTestRule.stringResource(BookmarksR.string.saved) private val saved by composeTestRule.stringResource(BookmarksR.string.feature_bookmarks_title)
private val settings by composeTestRule.stringResource(SettingsR.string.top_app_bar_action_icon_description) private val settings by composeTestRule.stringResource(SettingsR.string.feature_settings_top_app_bar_action_icon_description)
private val brand by composeTestRule.stringResource(SettingsR.string.brand_android) private val brand by composeTestRule.stringResource(SettingsR.string.feature_settings_brand_android)
private val ok by composeTestRule.stringResource(SettingsR.string.dismiss_dialog_button_text) private val ok by composeTestRule.stringResource(SettingsR.string.feature_settings_dismiss_dialog_button_text)
@Before @Before
fun setup() = hiltRule.inject() fun setup() = hiltRule.inject()
@ -166,7 +166,10 @@ class NavigationTest {
composeTestRule.apply { composeTestRule.apply {
// GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown. // GIVEN the user is on any of the top level destinations, THEN the Up arrow is not shown.
onNodeWithContentDescription(navigateUp).assertDoesNotExist() onNodeWithContentDescription(navigateUp).assertDoesNotExist()
// TODO: Add top level destinations here, see b/226357686.
onNodeWithText(saved).performClick()
onNodeWithContentDescription(navigateUp).assertDoesNotExist()
onNodeWithText(interests).performClick() onNodeWithText(interests).performClick()
onNodeWithContentDescription(navigateUp).assertDoesNotExist() 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.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.feature.bookmarks.R as bookmarksR 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.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 * Type for the top level destinations in the application. Each of these destinations
@ -37,19 +37,19 @@ enum class TopLevelDestination(
FOR_YOU( FOR_YOU(
selectedIcon = NiaIcons.Upcoming, selectedIcon = NiaIcons.Upcoming,
unselectedIcon = NiaIcons.UpcomingBorder, unselectedIcon = NiaIcons.UpcomingBorder,
iconTextId = forYouR.string.for_you, iconTextId = forYouR.string.feature_foryou_title,
titleTextId = R.string.app_name, titleTextId = R.string.app_name,
), ),
BOOKMARKS( BOOKMARKS(
selectedIcon = NiaIcons.Bookmarks, selectedIcon = NiaIcons.Bookmarks,
unselectedIcon = NiaIcons.BookmarksBorder, unselectedIcon = NiaIcons.BookmarksBorder,
iconTextId = bookmarksR.string.saved, iconTextId = bookmarksR.string.feature_bookmarks_title,
titleTextId = bookmarksR.string.saved, titleTextId = bookmarksR.string.feature_bookmarks_title,
), ),
INTERESTS( INTERESTS(
selectedIcon = NiaIcons.Grid3x3, selectedIcon = NiaIcons.Grid3x3,
unselectedIcon = NiaIcons.Grid3x3, unselectedIcon = NiaIcons.Grid3x3,
iconTextId = interestsR.string.interests, iconTextId = searchR.string.feature_search_interests,
titleTextId = interestsR.string.interests, titleTextId = searchR.string.feature_search_interests,
), ),
} }

@ -181,11 +181,11 @@ fun NiaApp(
titleRes = destination.titleTextId, titleRes = destination.titleTextId,
navigationIcon = NiaIcons.Search, navigationIcon = NiaIcons.Search,
navigationIconContentDescription = stringResource( 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, actionIcon = NiaIcons.Settings,
actionIconContentDescription = stringResource( 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( colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent, 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 * Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the
* route. * route.
*/ */
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.values().asList() val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.entries
/** /**
* The top level destinations that have unread news resources. * The top level destinations that have unread news resources.

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

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

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

@ -21,7 +21,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.kotlin
class AndroidFeatureConventionPlugin : Plugin<Project> { class AndroidFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) { override fun apply(target: Project) {
@ -39,27 +38,12 @@ class AndroidFeatureConventionPlugin : Plugin<Project> {
} }
dependencies { dependencies {
add("implementation", project(":core:model"))
add("implementation", project(":core:ui")) add("implementation", project(":core:ui"))
add("implementation", project(":core:designsystem")) 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.hilt.navigation.compose").get())
add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").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 { dependencies {
"implementation"(libs.findLibrary("hilt.android").get()) "implementation"(libs.findLibrary("hilt.android").get())
"ksp"(libs.findLibrary("hilt.compiler").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) { override fun apply(target: Project) {
with(target) { with(target) {
pluginManager.apply("com.android.library") pluginManager.apply("com.android.library")
// Screenshot Tests
pluginManager.apply("io.github.takahirom.roborazzi")
val extension = extensions.getByType<LibraryExtension>() val extension = extensions.getByType<LibraryExtension>()
configureAndroidCompose(extension) configureAndroidCompose(extension)

@ -41,6 +41,9 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
defaultConfig.targetSdk = 34 defaultConfig.targetSdk = 34
configureFlavors(this) configureFlavors(this)
configureGradleManagedDevices(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> { extensions.configure<LibraryAndroidComponentsExtension> {
configurePrintApksTask(this) configurePrintApksTask(this)
@ -48,9 +51,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
} }
dependencies { dependencies {
add("testImplementation", kotlin("test")) 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. * limitations under the License.
*/ */
import com.google.devtools.ksp.gradle.KspExtension import androidx.room.gradle.RoomExtension
import com.google.samples.apps.nowinandroid.libs import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project 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.configure
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.process.CommandLineArgumentProvider
import java.io.File
class AndroidRoomConventionPlugin : Plugin<Project> { class AndroidRoomConventionPlugin : Plugin<Project> {
override fun apply(target: Project) { override fun apply(target: Project) {
with(target) { with(target) {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp") 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. // The schemas directory contains a schema file for each version of the Room database.
// This is required to enable Room auto migrations. // This is required to enable Room auto migrations.
// See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration. // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration.
arg(RoomSchemaArgProvider(File(projectDir, "schemas"))) schemaDirectory("$projectDir/schemas")
} }
dependencies { 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() val bom = libs.findLibrary("androidx-compose-bom").get()
add("implementation", platform(bom)) add("implementation", platform(bom))
add("androidTestImplementation", 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 { testOptions {

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

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

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

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

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

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

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

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

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

@ -31,18 +31,16 @@ android {
} }
dependencies { dependencies {
api(projects.core.common)
api(projects.core.database)
api(projects.core.datastore)
api(projects.core.network)
implementation(projects.core.analytics) 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(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.datastoreTest)
testImplementation(projects.core.testing) testImplementation(projects.core.testing)
} }

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

@ -25,7 +25,7 @@ import dagger.hilt.components.SingletonComponent
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
interface UserNewsResourceRepositoryModule { internal interface UserNewsResourceRepositoryModule {
@Binds @Binds
fun bindsUserNewsResourceRepository( fun bindsUserNewsResourceRepository(
userDataRepository: CompositeUserNewsResourceRepository, 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.AnalyticsEvent.Param
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper 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 eventType = if (isBookmarked) "news_resource_saved" else "news_resource_unsaved"
val paramKey = if (isBookmarked) "saved_news_resource_id" else "unsaved_news_resource_id" val paramKey = if (isBookmarked) "saved_news_resource_id" else "unsaved_news_resource_id"
logEvent( 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 eventType = if (isFollowed) "topic_followed" else "topic_unfollowed"
val paramKey = if (isFollowed) "followed_topic_id" else "unfollowed_topic_id" val paramKey = if (isFollowed) "followed_topic_id" else "unfollowed_topic_id"
logEvent( logEvent(
@ -46,7 +46,7 @@ fun AnalyticsHelper.logTopicFollowToggled(followedTopicId: String, isFollowed: B
) )
} }
fun AnalyticsHelper.logThemeChanged(themeName: String) = internal fun AnalyticsHelper.logThemeChanged(themeName: String) =
logEvent( logEvent(
AnalyticsEvent( AnalyticsEvent(
type = "theme_changed", type = "theme_changed",
@ -56,7 +56,7 @@ fun AnalyticsHelper.logThemeChanged(themeName: String) =
), ),
) )
fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) = internal fun AnalyticsHelper.logDarkThemeConfigChanged(darkThemeConfigName: String) =
logEvent( logEvent(
AnalyticsEvent( AnalyticsEvent(
type = "dark_theme_config_changed", 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( logEvent(
AnalyticsEvent( AnalyticsEvent(
type = "dynamic_color_preference_changed", 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" val eventType = if (shouldHideOnboarding) "onboarding_complete" else "onboarding_reset"
logEvent( logEvent(
AnalyticsEvent(type = eventType), AnalyticsEvent(type = eventType),

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

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

@ -45,7 +45,7 @@ private const val SYNC_BATCH_SIZE = 40
* Disk storage backed implementation of the [NewsRepository]. * Disk storage backed implementation of the [NewsRepository].
* Reads are exclusively from local storage to support offline access. * 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 niaPreferencesDataSource: NiaPreferencesDataSource,
private val newsResourceDao: NewsResourceDao, private val newsResourceDao: NewsResourceDao,
private val topicDao: TopicDao, private val topicDao: TopicDao,

@ -34,7 +34,7 @@ import javax.inject.Inject
* Disk storage backed implementation of the [TopicsRepository]. * Disk storage backed implementation of the [TopicsRepository].
* Reads are exclusively from local storage to support offline access. * 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 topicDao: TopicDao,
private val network: NiaNetworkDataSource, private val network: NiaNetworkDataSource,
) : TopicsRepository { ) : TopicsRepository {

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

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

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

@ -28,7 +28,7 @@ import dagger.hilt.components.SingletonComponent
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object DaosModule { internal object DaosModule {
@Provides @Provides
fun providesTopicsDao( fun providesTopicsDao(
database: NiaDatabase, 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 * from and Y is the schema version you're migrating to. The class should implement
* `AutoMigrationSpec`. * `AutoMigrationSpec`.
*/ */
object DatabaseMigrations { internal object DatabaseMigrations {
@RenameColumn( @RenameColumn(
tableName = "topics", tableName = "topics",

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

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

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

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

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

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

@ -33,13 +33,12 @@ android {
} }
dependencies { dependencies {
api(libs.androidx.dataStore.core)
api(projects.core.datastoreProto) api(projects.core.datastoreProto)
api(projects.core.model)
implementation(projects.core.common) 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.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 * Migrates saved ids from [Int] to [String] types
*/ */
object IntToStringIdsMigration : DataMigration<UserPreferences> { internal object IntToStringIdsMigration : DataMigration<UserPreferences> {
override suspend fun cleanUp() = Unit override suspend fun cleanUp() = Unit

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

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

@ -17,6 +17,7 @@ plugins {
alias(libs.plugins.nowinandroid.android.library) alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.compose) alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco) alias(libs.plugins.nowinandroid.android.library.jacoco)
alias(libs.plugins.roborazzi)
} }
android { android {
@ -39,8 +40,15 @@ dependencies {
debugApi(libs.androidx.compose.ui.tooling) debugApi(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.core.ktx)
implementation(libs.coil.kt.compose) 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) androidTestImplementation(projects.core.testing)
} }

@ -50,7 +50,7 @@ fun DynamicAsyncImage(
imageUrl: String, imageUrl: String,
contentDescription: String?, contentDescription: String?,
modifier: Modifier = Modifier, 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 val iconTint = LocalTintTheme.current.iconTint
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }

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

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

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

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

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

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

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

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

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

@ -22,6 +22,6 @@ import javax.inject.Inject
/** /**
* Implementation of [Notifier] which does nothing. Useful for tests and previews. * 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 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. * Implementation of [Notifier] that displays notifications in the system tray.
*/ */
@Singleton @Singleton
class SystemTrayNotifier @Inject constructor( internal class SystemTrayNotifier @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
) : Notifier { ) : Notifier {
@ -66,7 +66,7 @@ class SystemTrayNotifier @Inject constructor(
val newsNotifications = truncatedNewsResources.map { newsResource -> val newsNotifications = truncatedNewsResources.map { newsResource ->
createNewsNotification { createNewsNotification {
setSmallIcon( 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) .setContentTitle(newsResource.title)
.setContentText(newsResource.content) .setContentText(newsResource.content)
@ -77,13 +77,13 @@ class SystemTrayNotifier @Inject constructor(
} }
val summaryNotification = createNewsNotification { val summaryNotification = createNewsNotification {
val title = getString( val title = getString(
R.string.news_notification_group_summary, R.string.core_notifications_news_notification_group_summary,
truncatedNewsResources.size, truncatedNewsResources.size,
) )
setContentTitle(title) setContentTitle(title)
.setContentText(title) .setContentText(title)
.setSmallIcon( .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. // Build summary info into InboxStyle template.
.setStyle(newsNotificationStyle(truncatedNewsResources, title)) .setStyle(newsNotificationStyle(truncatedNewsResources, title))
@ -140,10 +140,10 @@ private fun Context.ensureNotificationChannelExists() {
val channel = NotificationChannel( val channel = NotificationChannel(
NEWS_NOTIFICATION_CHANNEL_ID, NEWS_NOTIFICATION_CHANNEL_ID,
getString(R.string.news_notification_channel_name), getString(R.string.core_notifications_news_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT, NotificationManager.IMPORTANCE_DEFAULT,
).apply { ).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 // Register the channel with the system
NotificationManagerCompat.from(this).createNotificationChannel(channel) NotificationManagerCompat.from(this).createNotificationChannel(channel)

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="news_notification_channel_name">News updates</string> <string name="core_notifications_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="core_notifications_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_group_summary">%1$d news updates</string>
</resources> </resources>

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

@ -24,28 +24,23 @@ android {
} }
dependencies { dependencies {
api(libs.accompanist.testharness) api(kotlin("test"))
api(libs.androidx.activity.compose)
api(libs.androidx.compose.ui.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.roborazzi)
api(libs.robolectric.shadows) api(projects.core.analytics)
api(libs.turbine) api(projects.core.data)
api(projects.core.model)
api(projects.core.notifications)
debugApi(libs.androidx.compose.ui.testManifest) 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.common)
implementation(projects.core.data)
implementation(projects.core.designsystem) 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 @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object TestDispatcherModule { internal object TestDispatcherModule {
@Provides @Provides
@Singleton @Singleton
fun providesTestDispatcher(): TestDispatcher = UnconfinedTestDispatcher() fun providesTestDispatcher(): TestDispatcher = UnconfinedTestDispatcher()

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

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

@ -27,29 +27,14 @@ android {
} }
dependencies { 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.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.browser)
implementation(libs.androidx.core.ktx)
implementation(libs.coil.kt) implementation(libs.coil.kt)
implementation(libs.coil.kt.compose) implementation(libs.coil.kt.compose)
implementation(libs.kotlinx.datetime)
androidTestImplementation(projects.core.testing) androidTestImplementation(projects.core.testing)
} }

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

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

@ -15,15 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="bookmark">Bookmark</string> <string name="core_ui_bookmark">Bookmark</string>
<string name="unbookmark">Unbookmark</string> <string name="core_ui_unbookmark">Unbookmark</string>
<string name="back">Back</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="core_ui_card_tap_action">Open Resource Link</string>
<string name="card_meta_data_text">%1$s • %2$s</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="core_ui_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_not_followed">%1$s is not followed</string>
</resources> </resources>

@ -25,5 +25,9 @@ android {
} }
dependencies { 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 composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.saved_loading), composeTestRule.activity.resources.getString(R.string.feature_bookmarks_loading),
) )
.assertExists() .assertExists()
} }
@ -125,7 +125,7 @@ class BookmarksScreenTest {
composeTestRule composeTestRule
.onAllNodesWithContentDescription( .onAllNodesWithContentDescription(
composeTestRule.activity.getString( 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( ).filter(
hasAnyAncestor( hasAnyAncestor(
@ -156,13 +156,13 @@ class BookmarksScreenTest {
composeTestRule composeTestRule
.onNodeWithText( .onNodeWithText(
composeTestRule.activity.getString(R.string.bookmarks_empty_error), composeTestRule.activity.getString(R.string.feature_bookmarks_empty_error),
) )
.assertExists() .assertExists()
composeTestRule composeTestRule
.onNodeWithText( .onNodeWithText(
composeTestRule.activity.getString(R.string.bookmarks_empty_description), composeTestRule.activity.getString(R.string.feature_bookmarks_empty_description),
) )
.assertExists() .assertExists()
} }

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

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

@ -25,7 +25,13 @@ android {
} }
dependencies { dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.activity.compose)
implementation(libs.accompanist.permissions) 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 { private val doneButtonMatcher by lazy {
hasText( hasText(
composeTestRule.activity.resources.getString(R.string.done), composeTestRule.activity.resources.getString(R.string.feature_foryou_done),
) )
} }
@ -70,7 +70,7 @@ class ForYouScreenTest {
composeTestRule composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading), composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
) )
.assertExists() .assertExists()
} }
@ -96,7 +96,7 @@ class ForYouScreenTest {
composeTestRule composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading), composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
) )
.assertExists() .assertExists()
} }
@ -215,7 +215,7 @@ class ForYouScreenTest {
composeTestRule composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading), composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
) )
.assertExists() .assertExists()
} }
@ -241,7 +241,7 @@ class ForYouScreenTest {
composeTestRule composeTestRule
.onNodeWithContentDescription( .onNodeWithContentDescription(
composeTestRule.activity.resources.getString(R.string.for_you_loading), composeTestRule.activity.resources.getString(R.string.feature_foryou_loading),
) )
.assertExists() .assertExists()
} }

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

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

@ -22,3 +22,12 @@ plugins {
android { android {
namespace = "com.google.samples.apps.nowinandroid.feature.interests" 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 @Before
fun setup() { fun setup() {
composeTestRule.activity.apply { composeTestRule.activity.apply {
interestsLoading = getString(R.string.loading) interestsLoading = getString(R.string.feature_interests_loading)
interestsEmptyHeader = getString(R.string.empty_header) interestsEmptyHeader = getString(R.string.feature_interests_empty_header)
interestsTopicCardFollowButton = interestsTopicCardFollowButton =
getString(R.string.card_follow_button_content_desc) getString(R.string.feature_interests_card_follow_button_content_desc)
interestsTopicCardUnfollowButton = 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( Icon(
imageVector = NiaIcons.Add, imageVector = NiaIcons.Add,
contentDescription = stringResource( 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( Icon(
imageVector = NiaIcons.Check, imageVector = NiaIcons.Check,
contentDescription = stringResource( 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 -> InterestsUiState.Loading ->
NiaLoadingWheel( NiaLoadingWheel(
modifier = modifier, modifier = modifier,
contentDesc = stringResource(id = R.string.loading), contentDesc = stringResource(id = R.string.feature_interests_loading),
) )
is InterestsUiState.Interests -> is InterestsUiState.Interests ->
TopicsTabContent( TopicsTabContent(
@ -82,7 +82,7 @@ internal fun InterestsScreen(
@Composable @Composable
private fun InterestsEmptyScreen() { private fun InterestsEmptyScreen() {
Text(text = stringResource(id = R.string.empty_header)) Text(text = stringResource(id = R.string.feature_interests_empty_header))
} }
@DevicePreviews @DevicePreviews

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

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

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

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

@ -15,15 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<string name="search">Search</string> <string name="feature_search_title">Search</string>
<string name="clear_search_text_content_desc">Clear search text</string> <string name="feature_search_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="feature_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="feature_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="feature_search_try_another_search">Try another search or explorer </string>
<string name="interests">Interests</string> <string name="feature_search_interests">Interests</string>
<string name="to_browse_topics"> to browse topics</string> <string name="feature_search_to_browse_topics"> to browse topics</string>
<string name="topics">Topics</string> <string name="feature_search_topics">Topics</string>
<string name="updates">Updates</string> <string name="feature_search_updates">Updates</string>
<string name="recent_searches">Recent searches</string> <string name="feature_search_recent_searches">Recent searches</string>
<string name="clear_recent_searches_content_desc">Clear searches</string> <string name="feature_search_clear_recent_searches_content_desc">Clear searches</string>
</resources> </resources>

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

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