Merge branch 'main' into compose-bom-2023-06-00

pull/808/head
Milosz Moczkowski 1 year ago committed by GitHub
commit 7203460051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 UI, testing, architecture and more, and how all of these different pieces of the project fit
together to create a complete app. 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 # Architecture
The **Now in Android** app follows the The **Now in Android** app follows the

@ -119,6 +119,7 @@ dependencies {
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.window.manager) implementation(libs.androidx.window.manager)
implementation(libs.androidx.profileinstaller) implementation(libs.androidx.profileinstaller)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt) implementation(libs.coil.kt)
} }

@ -17,6 +17,7 @@
package com.google.samples.apps.nowinandroid package com.google.samples.apps.nowinandroid
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
@ -35,6 +36,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.metrics.performance.JankStats import androidx.metrics.performance.JankStats
import androidx.profileinstaller.ProfileVerifier
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.MainActivityUiState.Success import com.google.samples.apps.nowinandroid.MainActivityUiState.Success
@ -47,11 +49,16 @@ import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.ui.NiaApp import com.google.samples.apps.nowinandroid.ui.NiaApp
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "MainActivity"
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -133,12 +140,48 @@ class MainActivity : ComponentActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
lazyStats.get().isTrackingEnabled = true lazyStats.get().isTrackingEnabled = true
lifecycleScope.launch {
logCompilationStatus()
}
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
lazyStats.get().isTrackingEnabled = false lazyStats.get().isTrackingEnabled = false
} }
/**
* Logs the app's Baseline Profile Compilation Status using [ProfileVerifier].
*/
private suspend fun logCompilationStatus() {
/*
When delivering through Google Play, the baseline profile is compiled during installation.
In this case you will see the correct state logged without any further action necessary.
To verify baseline profile installation locally, you need to manually trigger baseline
profile installation.
For immediate compilation, call:
`adb shell cmd package compile -f -m speed-profile com.example.macrobenchmark.target`
You can also trigger background optimizations:
`adb shell pm bg-dexopt-job`
Both jobs run asynchronously and might take some time complete.
To see quick turnaround of the ProfileVerifier, we recommend using `speed-profile`.
If you don't do either of these steps, you might only see the profile status reported as
"enqueued for compilation" when running the sample locally.
*/
withContext(Dispatchers.IO) {
val status = ProfileVerifier.getCompilationStatusAsync().await()
Log.d(TAG, "ProfileInstaller status code: ${status.profileInstallResultCode}")
Log.d(
TAG,
when {
status.isCompiledWithProfile -> "ProfileInstaller: is compiled with profile"
status.hasProfileEnqueuedForCompilation() ->
"ProfileInstaller: Enqueued for compilation"
else -> "Profile not compiled or enqueued"
},
)
}
}
} }
/** /**

@ -16,7 +16,6 @@
package com.google.samples.apps.nowinandroid.baselineprofile package com.google.samples.apps.nowinandroid.baselineprofile
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule import androidx.benchmark.macro.junit4.BaselineProfileRule
import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.PACKAGE_NAME
import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen
@ -31,13 +30,12 @@ import org.junit.Test
/** /**
* Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`. * Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`.
*/ */
@ExperimentalBaselineProfilesApi
class BaselineProfileGenerator { class BaselineProfileGenerator {
@get:Rule val baselineProfileRule = BaselineProfileRule() @get:Rule val baselineProfileRule = BaselineProfileRule()
@Test @Test
fun generate() = fun generate() =
baselineProfileRule.collectBaselineProfile(PACKAGE_NAME) { baselineProfileRule.collect(PACKAGE_NAME) {
// This block defines the app's critical user journey. Here we are interested in // This block defines the app's critical user journey. Here we are interested in
// optimizing for app startup. But you can also navigate and scroll // optimizing for app startup. But you can also navigate and scroll
// through your most important UI. // through your most important UI.

@ -33,7 +33,7 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
extensions.configure<ApplicationExtension> { extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this) configureKotlinAndroid(this)
defaultConfig.targetSdk = 33 defaultConfig.targetSdk = 34
configureGradleManagedDevices(this) configureGradleManagedDevices(this)
} }
extensions.configure<ApplicationAndroidComponentsExtension> { extensions.configure<ApplicationAndroidComponentsExtension> {

@ -38,7 +38,7 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
extensions.configure<LibraryExtension> { extensions.configure<LibraryExtension> {
configureKotlinAndroid(this) configureKotlinAndroid(this)
defaultConfig.targetSdk = 33 defaultConfig.targetSdk = 34
configureFlavors(this) configureFlavors(this)
configureGradleManagedDevices(this) configureGradleManagedDevices(this)
} }

@ -33,7 +33,7 @@ internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *>, commonExtension: CommonExtension<*, *, *, *>,
) { ) {
commonExtension.apply { commonExtension.apply {
compileSdk = 33 compileSdk = 34
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 21

@ -57,7 +57,11 @@ fun LazyGridScope.newsFeed(
when (feedState) { when (feedState) {
NewsFeedUiState.Loading -> Unit NewsFeedUiState.Loading -> Unit
is NewsFeedUiState.Success -> { is NewsFeedUiState.Success -> {
items(feedState.feed, key = { it.id }) { userNewsResource -> items(
items = feedState.feed,
key = { it.id },
contentType = { "newsFeedItem" },
) { userNewsResource ->
val resourceUrl by remember { val resourceUrl by remember {
mutableStateOf(Uri.parse(userNewsResource.url)) mutableStateOf(Uri.parse(userNewsResource.url))
} }

@ -171,7 +171,7 @@ Using the above modularization strategy, the Now in Android app has the followin
<tr> <tr>
<td><code>core:ui</code> <td><code>core:ui</code>
</td> </td>
<td>Composite UI components and resources used by feature modules, such as the news feed. Unlike the <code>designsystem<code> module, it is dependent on the data layer since it renders models, like news resources. <td>Composite UI components and resources used by feature modules, such as the news feed. Unlike the <code>designsystem</code> module, it is dependent on the data layer since it renders models, like news resources.
</td> </td>
<td> <code>NewsFeed</code> <code>NewsResourceCardExpanded</code> <td> <code>NewsFeed</code> <code>NewsResourceCardExpanded</code>
</td> </td>

@ -182,7 +182,7 @@ internal fun ForYouScreen(
onTopicClick = onTopicClick, onTopicClick = onTopicClick,
) )
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }, contentType = "bottomSpacing") {
Column { Column {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// Add space for the content to clear the "offline" snackbar. // Add space for the content to clear the "offline" snackbar.
@ -240,7 +240,7 @@ private fun LazyGridScope.onboarding(
-> Unit -> Unit
is OnboardingUiState.Shown -> { is OnboardingUiState.Shown -> {
item(span = { GridItemSpan(maxLineSpan) }) { item(span = { GridItemSpan(maxLineSpan) }, contentType = "onboarding") {
Column(modifier = interestsItemModifier) { Column(modifier = interestsItemModifier) {
Text( Text(
text = stringResource(R.string.onboarding_guidance_title), text = stringResource(R.string.onboarding_guidance_title),

@ -17,9 +17,11 @@
package com.google.samples.apps.nowinandroid.feature.settings package com.google.samples.apps.nowinandroid.feature.settings
import android.content.Intent import android.content.Intent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -149,8 +151,9 @@ fun SettingsDialog(
) )
} }
// [ColumnScope] is used for using the [ColumnScope.AnimatedVisibility] extension overload composable.
@Composable @Composable
private fun SettingsPanel( private fun ColumnScope.SettingsPanel(
settings: UserEditableSettings, settings: UserEditableSettings,
supportDynamicColor: Boolean, supportDynamicColor: Boolean,
onChangeThemeBrand: (themeBrand: ThemeBrand) -> Unit, onChangeThemeBrand: (themeBrand: ThemeBrand) -> Unit,
@ -170,19 +173,21 @@ private fun SettingsPanel(
onClick = { onChangeThemeBrand(ANDROID) }, onClick = { onChangeThemeBrand(ANDROID) },
) )
} }
if (settings.brand == DEFAULT && supportDynamicColor) { AnimatedVisibility(visible = settings.brand == DEFAULT && supportDynamicColor) {
SettingsDialogSectionTitle(text = stringResource(R.string.dynamic_color_preference)) Column {
Column(Modifier.selectableGroup()) { SettingsDialogSectionTitle(text = stringResource(R.string.dynamic_color_preference))
SettingsDialogThemeChooserRow( Column(Modifier.selectableGroup()) {
text = stringResource(string.dynamic_color_yes), SettingsDialogThemeChooserRow(
selected = settings.useDynamicColor, text = stringResource(string.dynamic_color_yes),
onClick = { onChangeDynamicColorPreference(true) }, selected = settings.useDynamicColor,
) onClick = { onChangeDynamicColorPreference(true) },
SettingsDialogThemeChooserRow( )
text = stringResource(string.dynamic_color_no), SettingsDialogThemeChooserRow(
selected = !settings.useDynamicColor, text = stringResource(string.dynamic_color_no),
onClick = { onChangeDynamicColorPreference(false) }, selected = !settings.useDynamicColor,
) onClick = { onChangeDynamicColorPreference(false) },
)
}
} }
} }
SettingsDialogSectionTitle(text = stringResource(R.string.dark_mode_preference)) SettingsDialogSectionTitle(text = stringResource(R.string.dark_mode_preference))

@ -17,7 +17,7 @@ androidxLifecycle = "2.6.1"
androidxMacroBenchmark = "1.1.1" androidxMacroBenchmark = "1.1.1"
androidxMetrics = "1.0.0-alpha03" androidxMetrics = "1.0.0-alpha03"
androidxNavigation = "2.5.3" androidxNavigation = "2.5.3"
androidxProfileinstaller = "1.3.0" androidxProfileinstaller = "1.3.1"
androidxStartup = "1.1.1" androidxStartup = "1.1.1"
androidxTestCore = "1.5.0" androidxTestCore = "1.5.0"
androidxTestExt = "1.1.4" androidxTestExt = "1.1.4"
@ -116,6 +116,7 @@ hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hi
junit4 = { group = "junit", name = "junit", version.ref = "junit4" } junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }

Loading…
Cancel
Save