diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 10df770bf..39ac1090f 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -131,8 +131,8 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 - - name: Setup Android SDK - uses: android-actions/setup-android@v2 + - name: Accept Android licenses + run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true - name: Build AndroidTest apps run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest diff --git a/README.md b/README.md index 9aca22cbd..1b1fb795e 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,6 @@ understanding of which libraries and tools are being used, the reasoning behind UI, testing, architecture and more, and how all of these different pieces of the project fit together to create a complete app. -NOTE: Building the app using an M1 Mac will require the use of -[Rosetta](https://support.apple.com/en-gb/HT211861). See -[the following bug](https://github.com/protocolbuffers/protobuf/issues/9397#issuecomment-1086138036) -for more details. - # Architecture The **Now in Android** app follows the diff --git a/app-nia-catalog/proguard-rules.pro b/app-nia-catalog/proguard-rules.pro deleted file mode 100644 index ff59496d8..000000000 --- a/app-nia-catalog/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 41012b47a..dcaf39ce7 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,30 +1,3 @@ -# Keep `Companion` object fields of serializable classes. -# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. --if @kotlinx.serialization.Serializable class ** --keepclassmembers class <1> { - static <1>$Companion Companion; -} - -# Keep `serializer()` on companion objects (both default and named) of serializable classes. --if @kotlinx.serialization.Serializable class ** { - static **$* *; -} --keepclassmembers class <2>$<3> { - kotlinx.serialization.KSerializer serializer(...); -} - -# Keep `INSTANCE.serializer()` of serializable objects. --if @kotlinx.serialization.Serializable class ** { - public static ** INSTANCE; -} --keepclassmembers class <1> { - public static <1> INSTANCE; - kotlinx.serialization.KSerializer serializer(...); -} - -# @Serializable and @Polymorphic are used at runtime for polymorphic serialization. --keepattributes RuntimeVisibleAnnotations,AnnotationDefault - -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt index 7b3a0059f..422592b8a 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt @@ -16,12 +16,11 @@ import com.android.build.api.dsl.ApplicationExtension import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType class AndroidApplicationFirebaseConventionPlugin : Plugin { override fun apply(target: Project) { @@ -32,7 +31,6 @@ class AndroidApplicationFirebaseConventionPlugin : Plugin { apply("com.google.firebase.crashlytics") } - val libs = extensions.getByType().named("libs") dependencies { val bom = libs.findLibrary("firebase-bom").get() add("implementation", platform(bom)) diff --git a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt index 1b567ae2d..cc42d60fd 100644 --- a/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt @@ -14,15 +14,13 @@ * limitations under the License. */ -import com.android.build.api.dsl.ApplicationExtension import com.android.build.gradle.LibraryExtension import com.google.samples.apps.nowinandroid.configureGradleManagedDevices +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.kotlin class AndroidFeatureConventionPlugin : Plugin { @@ -40,8 +38,6 @@ class AndroidFeatureConventionPlugin : Plugin { configureGradleManagedDevices(this) } - val libs = extensions.getByType().named("libs") - dependencies { add("implementation", project(":core:model")) add("implementation", project(":core:ui")) diff --git a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt index 29cb748c2..b98673619 100644 --- a/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt @@ -14,11 +14,10 @@ * limitations under the License. */ +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType class AndroidHiltConventionPlugin : Plugin { override fun apply(target: Project) { @@ -30,7 +29,6 @@ class AndroidHiltConventionPlugin : Plugin { apply("org.jetbrains.kotlin.kapt") } - val libs = extensions.getByType().named("libs") dependencies { "implementation"(libs.findLibrary("hilt.android").get()) "kapt"(libs.findLibrary("hilt.compiler").get()) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 83ce09324..e9ee03be9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -21,12 +21,11 @@ import com.google.samples.apps.nowinandroid.configureGradleManagedDevices import com.google.samples.apps.nowinandroid.configureKotlinAndroid import com.google.samples.apps.nowinandroid.configurePrintApksTask import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.kotlin class AndroidLibraryConventionPlugin : Plugin { @@ -48,7 +47,6 @@ class AndroidLibraryConventionPlugin : Plugin { configurePrintApksTask(this) disableUnnecessaryAndroidTests(target) } - val libs = extensions.getByType().named("libs") configurations.configureEach { resolutionStrategy { force(libs.findLibrary("junit4").get()) diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index 778798b96..b67fb1b26 100644 --- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -15,15 +15,14 @@ */ import com.google.devtools.ksp.gradle.KspExtension +import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.gradle.process.CommandLineArgumentProvider import java.io.File @@ -40,7 +39,6 @@ class AndroidRoomConventionPlugin : Plugin { arg(RoomSchemaArgProvider(File(projectDir, "schemas"))) } - val libs = extensions.getByType().named("libs") dependencies { add("implementation", libs.findLibrary("room.runtime").get()) add("implementation", libs.findLibrary("room.ktx").get()) diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt index 5997f7d4e..9950352b1 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.kt @@ -18,9 +18,7 @@ package com.google.samples.apps.nowinandroid import com.android.build.api.dsl.CommonExtension import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File @@ -31,8 +29,6 @@ import java.io.File internal fun Project.configureAndroidCompose( commonExtension: CommonExtension<*, *, *, *>, ) { - val libs = extensions.getByType().named("libs") - commonExtension.apply { buildFeatures { compose = true diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt index d801d7b69..70eef1a2d 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt @@ -18,10 +18,8 @@ package com.google.samples.apps.nowinandroid import com.android.build.api.variant.AndroidComponentsExtension import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType import org.gradle.testing.jacoco.plugins.JacocoPluginExtension @@ -44,8 +42,6 @@ private fun String.capitalize() = replaceFirstChar { internal fun Project.configureJacoco( androidComponentsExtension: AndroidComponentsExtension<*, *, *>, ) { - val libs = extensions.getByType().named("libs") - configure { toolVersion = libs.findVersion("jacoco").get().toString() } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt index edffeeda7..5cdf2f593 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt @@ -19,11 +19,9 @@ package com.google.samples.apps.nowinandroid import com.android.build.api.dsl.CommonExtension import org.gradle.api.JavaVersion import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.plugins.JavaPluginExtension import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -52,8 +50,6 @@ internal fun Project.configureKotlinAndroid( configureKotlin() - val libs = extensions.getByType().named("libs") - dependencies { add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) } @@ -88,6 +84,9 @@ private fun Project.configureKotlin() { allWarningsAsErrors = warningsAsErrors.toBoolean() freeCompilerArgs = freeCompilerArgs + listOf( "-opt-in=kotlin.RequiresOptIn", + // Enable experimental coroutines APIs, including Flow + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.FlowPreview", ) } } diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt new file mode 100644 index 000000000..e45d7f2e1 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs + get(): VersionCatalog = extensions.getByType().named("libs") diff --git a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt index 58ec216fd..16cd3edf7 100644 --- a/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt +++ b/core/ui/src/main/java/com/google/samples/apps/nowinandroid/core/ui/NewsFeed.kt @@ -57,7 +57,11 @@ fun LazyGridScope.newsFeed( when (feedState) { NewsFeedUiState.Loading -> Unit is NewsFeedUiState.Success -> { - items(feedState.feed, key = { it.id }) { userNewsResource -> + items( + items = feedState.feed, + key = { it.id }, + contentType = { "newsFeedItem" }, + ) { userNewsResource -> val resourceUrl by remember { mutableStateOf(Uri.parse(userNewsResource.url)) } diff --git a/docs/ModularizationLearningJourney.md b/docs/ModularizationLearningJourney.md index 81e35c436..a9766c68d 100644 --- a/docs/ModularizationLearningJourney.md +++ b/docs/ModularizationLearningJourney.md @@ -159,13 +159,21 @@ Using the above modularization strategy, the Now in Android app has the followin TopicsRepository
+ + core:designsystem + + Design system which includes Core UI components (many of which are customized Material 3 components), app theme and icons. The design system can be viewed by running the app-nia-catalog run configuration. + + + NiaIcons NiaButton NiaTheme + + core:ui - UI components, composables and resources, such as icons, used by different features. + Composite UI components and resources used by feature modules, such as the news feed. Unlike the designsystem module, it is dependent on the data layer since it renders models, like news resources. - NiaIcons
- NewsResourceCardExpanded + NewsFeed NewsResourceCardExpanded diff --git a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt index 70cc7e541..eaa0c58fa 100644 --- a/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt +++ b/feature/foryou/src/main/java/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreen.kt @@ -182,7 +182,7 @@ internal fun ForYouScreen( onTopicClick = onTopicClick, ) - item(span = { GridItemSpan(maxLineSpan) }) { + item(span = { GridItemSpan(maxLineSpan) }, contentType = "bottomSpacing") { Column { Spacer(modifier = Modifier.height(8.dp)) // Add space for the content to clear the "offline" snackbar. @@ -240,7 +240,7 @@ private fun LazyGridScope.onboarding( -> Unit is OnboardingUiState.Shown -> { - item(span = { GridItemSpan(maxLineSpan) }) { + item(span = { GridItemSpan(maxLineSpan) }, contentType = "onboarding") { Column(modifier = interestsItemModifier) { Text( text = stringResource(R.string.onboarding_guidance_title), diff --git a/feature/settings/proguard-rules.pro b/feature/settings/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/feature/settings/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt index d8411113d..7fff6feaf 100644 --- a/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/java/com/google/samples/apps/nowinandroid/feature/settings/SettingsDialog.kt @@ -17,9 +17,11 @@ package com.google.samples.apps.nowinandroid.feature.settings import android.content.Intent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row @@ -149,8 +151,9 @@ fun SettingsDialog( ) } +// [ColumnScope] is used for using the [ColumnScope.AnimatedVisibility] extension overload composable. @Composable -private fun SettingsPanel( +private fun ColumnScope.SettingsPanel( settings: UserEditableSettings, supportDynamicColor: Boolean, onChangeThemeBrand: (themeBrand: ThemeBrand) -> Unit, @@ -170,19 +173,21 @@ private fun SettingsPanel( onClick = { onChangeThemeBrand(ANDROID) }, ) } - if (settings.brand == DEFAULT && supportDynamicColor) { - SettingsDialogSectionTitle(text = stringResource(R.string.dynamic_color_preference)) - Column(Modifier.selectableGroup()) { - SettingsDialogThemeChooserRow( - text = stringResource(string.dynamic_color_yes), - selected = settings.useDynamicColor, - onClick = { onChangeDynamicColorPreference(true) }, - ) - SettingsDialogThemeChooserRow( - text = stringResource(string.dynamic_color_no), - selected = !settings.useDynamicColor, - onClick = { onChangeDynamicColorPreference(false) }, - ) + AnimatedVisibility(visible = settings.brand == DEFAULT && supportDynamicColor) { + Column { + SettingsDialogSectionTitle(text = stringResource(R.string.dynamic_color_preference)) + Column(Modifier.selectableGroup()) { + SettingsDialogThemeChooserRow( + text = stringResource(string.dynamic_color_yes), + selected = settings.useDynamicColor, + onClick = { onChangeDynamicColorPreference(true) }, + ) + SettingsDialogThemeChooserRow( + text = stringResource(string.dynamic_color_no), + selected = !settings.useDynamicColor, + onClick = { onChangeDynamicColorPreference(false) }, + ) + } } } SettingsDialogSectionTitle(text = stringResource(R.string.dark_mode_preference)) diff --git a/gradle.properties b/gradle.properties index 10ff2e365..b57dc01ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ org.gradle.configureondemand=false org.gradle.caching=true # Enable configuration caching between builds. -org.gradle.unsafe.configuration-cache=true +org.gradle.configuration-cache=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app"s APK @@ -35,9 +35,6 @@ kotlin.code.style=official android.nonTransitiveRClass=true # Disable build features that are enabled by default, -# https://developer.android.com/studio/releases/gradle-plugin#buildFeatures -android.defaults.buildfeatures.buildconfig=false -android.defaults.buildfeatures.aidl=false -android.defaults.buildfeatures.renderscript=false +# https://developer.android.com/build/releases/gradle-plugin#default-changes android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false