From 4fee0a317633eeb23cae4573850e25b9333009ad Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Thu, 9 Apr 2026 11:58:20 +0200 Subject: [PATCH] Update to use rememberAmbientModeManager --- .../exercisesamplecompose/app/MainActivity.kt | 23 ++++++---- .../presentation/ambient/AmbientUI.kt | 18 ++++---- .../presentation/exercise/ExerciseScreen.kt | 36 +++++++-------- .../preparing/PreparingExerciseScreen.kt | 34 +++++++------- .../presentation/ExerciseScreenTest.kt | 45 +++++++++---------- .../PreparingExerciseScreenTest.kt | 6 +-- .../gradle/libs.versions.toml | 21 ++++----- 7 files changed, 94 insertions(+), 89 deletions(-) diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/app/MainActivity.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/app/MainActivity.kt index c8598bb9..a11de048 100644 --- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/app/MainActivity.kt +++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/app/MainActivity.kt @@ -20,6 +20,9 @@ import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.CompositionLocalProvider +import androidx.wear.compose.foundation.LocalAmbientModeManager +import androidx.wear.compose.foundation.rememberAmbientModeManager import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.fragment.app.FragmentActivity import androidx.navigation.NavHostController @@ -58,16 +61,20 @@ class MainActivity : FragmentActivity() { requestPermissions.launch(PreparingViewModel.permissions.toTypedArray()) setContent { - navController = rememberSwipeDismissableNavController() + val ambientModeManager = rememberAmbientModeManager() - ExerciseSampleApp( - navController, - onFinishActivity = { this.finish() } - ) + CompositionLocalProvider(LocalAmbientModeManager provides ambientModeManager) { + navController = rememberSwipeDismissableNavController() - LaunchedEffect(Unit) { - prepareIfNoExercise() - pendingNavigation = false + ExerciseSampleApp( + navController, + onFinishActivity = { this.finish() } + ) + + LaunchedEffect(Unit) { + prepareIfNoExercise() + pendingNavigation = false + } } } } diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/ambient/AmbientUI.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/ambient/AmbientUI.kt index 90733e1b..eecd1b02 100644 --- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/ambient/AmbientUI.kt +++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/ambient/AmbientUI.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.withSaveLayer -import com.google.android.horologist.compose.ambient.AmbientState + /** * A Paint object configured to apply a grayscale effect. @@ -47,18 +47,18 @@ private val grayscale = /** * Applies a grayscale effect and scales down the content when in ambient mode. * - * This modifier checks the provided [AmbientState] to determine if the device is + * This modifier checks the provided [isAmbient] to determine if the device is * in ambient mode. If it is, the content is scaled down by 10% and a grayscale * filter is applied. When not in ambient mode, the content is rendered normally. */ -fun Modifier.ambientGray(ambientState: AmbientState): Modifier = +fun Modifier.ambientGray(isAmbient: Boolean): Modifier = graphicsLayer { - if (ambientState.isAmbient) { + if (isAmbient) { scaleX = 0.9f scaleY = 0.9f } }.drawWithContent { - if (ambientState.isAmbient) { + if (isAmbient) { drawIntoCanvas { it.withSaveLayer(size.toRect(), grayscale) { drawContent() @@ -70,14 +70,14 @@ fun Modifier.ambientGray(ambientState: AmbientState): Modifier = } /** - * This modifier conditionally draws the content based on the state provided by an [AmbientState]. + * This modifier conditionally draws the content based on whether it is in ambient mode. * - * If the `isInteractive` property of the provided [ambientState] is true, the content will be drawn. + * If [isAmbient] is false, the content will be drawn. * Otherwise, the content will not be drawn, effectively leaving the area blank. */ -fun Modifier.ambientBlank(ambientState: AmbientState): Modifier = +fun Modifier.ambientBlank(isAmbient: Boolean): Modifier = drawWithContent { - if (ambientState.isInteractive) { + if (!isAmbient) { drawContent() } } diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/exercise/ExerciseScreen.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/exercise/ExerciseScreen.kt index fd1b02a5..80ef27e8 100644 --- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/exercise/ExerciseScreen.kt +++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/exercise/ExerciseScreen.kt @@ -37,6 +37,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.wear.compose.foundation.AmbientMode +import androidx.wear.compose.foundation.LocalAmbientModeManager import androidx.wear.compose.foundation.pager.HorizontalPager import androidx.wear.compose.foundation.pager.rememberPagerState import androidx.wear.compose.material3.AlertDialog @@ -49,7 +51,7 @@ import androidx.wear.compose.material3.Text import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices import com.example.exercisesamplecompose.R import com.example.exercisesamplecompose.data.ServiceState -import com.example.exercisesamplecompose.presentation.ambient.ambientBlank +import com.example.exercisesamplecompose.presentation.ambient.ambientGray import com.example.exercisesamplecompose.presentation.component.CaloriesText import com.example.exercisesamplecompose.presentation.component.DistanceText import com.example.exercisesamplecompose.presentation.component.HRText @@ -63,8 +65,6 @@ import com.example.exercisesamplecompose.presentation.summary.SummaryScreenState import com.example.exercisesamplecompose.presentation.theme.ThemePreview import com.example.exercisesamplecompose.service.ExerciseServiceState import com.google.android.horologist.annotations.ExperimentalHorologistApi -import com.google.android.horologist.compose.ambient.AmbientAware -import com.google.android.horologist.compose.ambient.AmbientState import com.google.android.horologist.health.composables.ActiveDurationText import kotlinx.coroutines.launch @@ -91,17 +91,17 @@ fun ExerciseRoute( uiState = uiState ) } else { - AmbientAware { ambientState -> - ExerciseScreen( - ambientState = ambientState, - onPauseClick = { viewModel.pauseExercise() }, - onEndClick = { viewModel.endExercise() }, - onResumeClick = { viewModel.resumeExercise() }, - onStartClick = { viewModel.startExercise() }, - uiState = uiState, - modifier = modifier - ) - } + val isAmbient = LocalAmbientModeManager.current?.currentAmbientMode is AmbientMode.Ambient + + ExerciseScreen( + isAmbient = isAmbient, + onPauseClick = { viewModel.pauseExercise() }, + onEndClick = { viewModel.endExercise() }, + onResumeClick = { viewModel.resumeExercise() }, + onStartClick = { viewModel.startExercise() }, + uiState = uiState, + modifier = modifier + ) } } @@ -149,7 +149,7 @@ fun ErrorStartingExerciseScreen( */ @Composable fun ExerciseScreen( - ambientState: AmbientState, + isAmbient: Boolean, onPauseClick: () -> Unit, onEndClick: () -> Unit, onResumeClick: () -> Unit, @@ -165,7 +165,7 @@ fun ExerciseScreen( HorizontalPager( state = pagerState ) { page -> - ScreenScaffold { + ScreenScaffold(modifier = Modifier.ambientGray(isAmbient)) { if (page == 0) { ExerciseControlButtons( uiState = uiState, @@ -320,6 +320,7 @@ private fun DurationRow(uiState: ExerciseScreenState) { fun ExerciseScreenPreview() { ThemePreview { ExerciseScreen( + isAmbient = false, onPauseClick = {}, onEndClick = {}, onResumeClick = {}, @@ -331,8 +332,7 @@ fun ExerciseScreenPreview() { ExerciseServiceState() ), exerciseState = ExerciseServiceState() - ), - ambientState = AmbientState.Interactive + ) ) } } diff --git a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/preparing/PreparingExerciseScreen.kt b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/preparing/PreparingExerciseScreen.kt index f1c7e233..e3440a92 100644 --- a/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/preparing/PreparingExerciseScreen.kt +++ b/health-services/ExerciseSampleCompose/app/src/main/java/com/example/exercisesamplecompose/presentation/preparing/PreparingExerciseScreen.kt @@ -65,8 +65,8 @@ import com.example.exercisesamplecompose.presentation.ambient.ambientGray import com.example.exercisesamplecompose.presentation.dialogs.ExerciseInProgressAlert import com.example.exercisesamplecompose.presentation.theme.ThemePreview import com.example.exercisesamplecompose.service.ExerciseServiceState -import com.google.android.horologist.compose.ambient.AmbientAware -import com.google.android.horologist.compose.ambient.AmbientState +import androidx.wear.compose.foundation.AmbientMode +import androidx.wear.compose.foundation.LocalAmbientModeManager import com.google.android.horologist.compose.layout.ColumnItemType import com.google.android.horologist.compose.layout.rememberResponsiveColumnPadding @@ -105,17 +105,17 @@ fun PreparingExerciseRoute( } } - AmbientAware { ambientState -> - PreparingExerciseScreen( - onStart = { - viewModel.startExercise() - onStart() - }, - uiState = uiState, - onGoals = { onGoals() }, - ambientState = ambientState - ) - } + val isAmbient = LocalAmbientModeManager.current?.currentAmbientMode is AmbientMode.Ambient + + PreparingExerciseScreen( + onStart = { + viewModel.startExercise() + onStart() + }, + uiState = uiState, + onGoals = { onGoals() }, + isAmbient = isAmbient + ) if (uiState.isTrackingInAnotherApp) { var dismissed by remember { mutableStateOf(false) } @@ -133,7 +133,7 @@ fun PreparingExerciseRoute( @Composable fun PreparingExerciseScreen( uiState: PreparingScreenState, - ambientState: AmbientState, + isAmbient: Boolean, onStart: () -> Unit = {}, onGoals: () -> Unit = {} ) { @@ -149,7 +149,7 @@ fun PreparingExerciseScreen( timeText = {}, contentPadding = contentPadding, modifier = Modifier - .ambientGray(ambientState) + .ambientGray(isAmbient) ) { contentPadding -> LocationStatusText( updatePrepareLocationStatus( @@ -247,7 +247,7 @@ fun PreparingExerciseScreenPreview() { requiredPermissions = PreparingViewModel.permissions, hasExerciseCapabilities = true ), - ambientState = AmbientState.Interactive + isAmbient = false ) } } @@ -265,7 +265,7 @@ fun PreparingExerciseScreenPreviewAmbient() { requiredPermissions = PreparingViewModel.permissions, hasExerciseCapabilities = true ), - ambientState = AmbientState.Ambient() + isAmbient = true ) } } diff --git a/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/ExerciseScreenTest.kt b/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/ExerciseScreenTest.kt index ef6a10c5..7e3d7f51 100644 --- a/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/ExerciseScreenTest.kt +++ b/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/ExerciseScreenTest.kt @@ -15,14 +15,13 @@ */ package com.example.exercisesamplecompose.presentation -import androidx.compose.runtime.CompositionLocalProvider + import androidx.wear.compose.material3.AppScaffold import com.example.exercisesamplecompose.data.ServiceState import com.example.exercisesamplecompose.presentation.exercise.ExerciseScreen import com.example.exercisesamplecompose.presentation.exercise.ExerciseScreenState import com.example.exercisesamplecompose.service.ExerciseServiceState -import com.google.android.horologist.compose.ambient.AmbientState -import com.google.android.horologist.compose.ambient.LocalAmbientState + import com.google.android.horologist.compose.layout.ResponsiveTimeText import com.google.android.horologist.screenshots.FixedTimeSource import com.google.android.horologist.screenshots.rng.WearDevice @@ -55,7 +54,7 @@ class ExerciseScreenTest( timeText = { ResponsiveTimeText(timeSource = FixedTimeSource) } ) { ExerciseScreen( - ambientState = AmbientState.Interactive, + isAmbient = false, onPauseClick = {}, onEndClick = {}, onResumeClick = {}, @@ -80,28 +79,26 @@ class ExerciseScreenTest( // Only run for one variant Assume.assumeTrue(device == WearDevice.GooglePixelWatch) - CompositionLocalProvider(LocalAmbientState provides AmbientState.Ambient()) { - AppScaffold( - timeText = { ResponsiveTimeText(timeSource = FixedTimeSource) } - ) { - ExerciseScreen( - onPauseClick = {}, - onEndClick = {}, - onResumeClick = {}, - onStartClick = {}, - uiState = - ExerciseScreenState( - hasExerciseCapabilities = true, - isTrackingAnotherExercise = false, - serviceState = - ServiceState.Connected( - ExerciseServiceState() - ), - exerciseState = ExerciseServiceState() + AppScaffold( + timeText = { ResponsiveTimeText(timeSource = FixedTimeSource) } + ) { + ExerciseScreen( + isAmbient = true, + onPauseClick = {}, + onEndClick = {}, + onResumeClick = {}, + onStartClick = {}, + uiState = + ExerciseScreenState( + hasExerciseCapabilities = true, + isTrackingAnotherExercise = false, + serviceState = + ServiceState.Connected( + ExerciseServiceState() ), - ambientState = AmbientState.Ambient() + exerciseState = ExerciseServiceState() ) - } + ) } } } diff --git a/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/PreparingExerciseScreenTest.kt b/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/PreparingExerciseScreenTest.kt index b79d661a..d8169619 100644 --- a/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/PreparingExerciseScreenTest.kt +++ b/health-services/ExerciseSampleCompose/app/src/test/java/com/example/exercisesamplecompose/presentation/PreparingExerciseScreenTest.kt @@ -22,7 +22,7 @@ import com.example.exercisesamplecompose.presentation.preparing.PreparingExercis import com.example.exercisesamplecompose.presentation.preparing.PreparingScreenState import com.example.exercisesamplecompose.presentation.preparing.PreparingViewModel import com.example.exercisesamplecompose.service.ExerciseServiceState -import com.google.android.horologist.compose.ambient.AmbientState + import com.google.android.horologist.screenshots.rng.WearDevice import com.google.android.horologist.screenshots.rng.WearDeviceScreenshotTest import org.junit.Assume @@ -71,7 +71,7 @@ class PreparingExerciseScreenTest( requiredPermissions = PreparingViewModel.permissions, hasExerciseCapabilities = true ), - ambientState = AmbientState.Interactive + isAmbient = false ) } } @@ -94,7 +94,7 @@ class PreparingExerciseScreenTest( requiredPermissions = PreparingViewModel.permissions, hasExerciseCapabilities = true ), - ambientState = AmbientState.Ambient() + isAmbient = true ) } } diff --git a/health-services/ExerciseSampleCompose/gradle/libs.versions.toml b/health-services/ExerciseSampleCompose/gradle/libs.versions.toml index 442aeecf..8d53651a 100644 --- a/health-services/ExerciseSampleCompose/gradle/libs.versions.toml +++ b/health-services/ExerciseSampleCompose/gradle/libs.versions.toml @@ -1,13 +1,14 @@ [versions] -android-gradle-plugin = "8.10.1" -androidx-activity = "1.9.2" -androidx-compose = "1.7.8" -androidx-lifecycle = "2.8.6" -androidx-wear-compose = "1.5.0-beta04" +android-gradle-plugin = "8.13.2" +androidx-activity = "1.13.0" +androidx-compose = "1.10.6" +androidx-compose-material = "1.7.8" +androidx-lifecycle = "2.10.0" +androidx-wear-compose = "1.6.1" core-splashscreen = "1.0.1" dagger = "2.56.2" health-services = "1.0.0" -horologist = "0.7.14-beta" +horologist = "0.7.15" kotlin = "2.1.21" org-jetbrains-kotlin = "2.1.21" ui-test-junit4 = "1.8.3" @@ -31,8 +32,8 @@ androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-kt androidx-lifecycle-runtime-compose = "androidx.lifecycle:lifecycle-runtime-compose:2.8.7" androidx-lifecycle-service = {module = "androidx.lifecycle:lifecycle-service", version.ref = "androidx-lifecycle"} androidx-lifecycle-viewmodel = {module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle"} -androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidx-compose" } -androidx-wear = "androidx.wear:wear:1.3.0" +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidx-compose-material" } +androidx-wear = "androidx.wear:wear:1.4.0" androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "ui-test-junit4" } androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "ui-test-junit4" } compose-compiler = "androidx.compose.compiler:compiler:1.5.15" @@ -42,7 +43,7 @@ compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" compose-wear-navigation = "androidx.wear.compose:compose-navigation:1.4.0" dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "dagger"} dagger-hilt-android-compiler = {module = "com.google.dagger:hilt-android-compiler", version.ref = "dagger"} -desugar-jdk-libs = "com.android.tools:desugar_jdk_libs:2.1.2" +desugar-jdk-libs = "com.android.tools:desugar_jdk_libs:2.1.5" guava = "com.google.guava:guava:33.3.1-android" hilt-navigation-compose = "androidx.hilt:hilt-navigation-compose:1.2.0" hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } @@ -60,7 +61,7 @@ test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1" test-ext-junit = "androidx.test.ext:junit:1.2.1" wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "androidx-wear-compose" } wear-compose-material = { module = "androidx.wear.compose:compose-material3", version.ref = "androidx-wear-compose" } -wear-ongoing-activity = "androidx.wear:wear-ongoing:1.0.0" +wear-ongoing-activity = "androidx.wear:wear-ongoing:1.1.0" [plugins] com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }