Loading packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 * * http://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.android.systemui.touchpad.tutorial.ui.composable import androidx.compose.runtime.saveable.SaverScope import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class TutorialActionStateSaverTest : SysuiTestCase() { private val saver = TutorialActionState.stateSaver() private val saverScope: SaverScope = object : SaverScope { override fun canBeSaved(value: Any) = true } @Test fun inProgressIsRestoredToNotStartedState() { assertRestoredState( savedState = InProgress(progress = 0f), expectedRestoredState = NotStarted, ) } @Test fun inProgressErrorIsRestoredToErrorState() { assertRestoredState( savedState = InProgressAfterError(InProgress(progress = 0f)), expectedRestoredState = Error, ) } @Test fun otherStatesAreRestoredToTheSameState() { assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted) assertRestoredState(savedState = Error, expectedRestoredState = Error) assertRestoredState( savedState = Finished(successAnimation = R.raw.trackpad_home_success), expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success), ) } private fun assertRestoredState( savedState: TutorialActionState, expectedRestoredState: TutorialActionState, ) { val savedValue = with(saver) { saverScope.save(savedState) } assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState) } } packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt +5 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester Loading @@ -45,7 +46,10 @@ import com.android.systemui.res.R fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { BackHandler(onBack = onBack) val screenConfig = buildScreenConfig() var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) } var actionState: TutorialActionState by rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { mutableStateOf(NotStarted) } val focusRequester = remember { FocusRequester() } Box( modifier = Loading packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt +27 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.mapSaver import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester Loading Loading @@ -69,6 +71,31 @@ sealed interface TutorialActionState { data class InProgressAfterError(val inProgress: InProgress) : TutorialActionState, Progress by inProgress companion object { fun stateSaver(): Saver<TutorialActionState, Any> { val classKey = "class" val successAnimationKey = "animation" return mapSaver( save = { buildMap { put(classKey, it::class.java.name) if (it is Finished) put(successAnimationKey, it.successAnimation) } }, restore = { map -> when (map[classKey] as? String) { NotStarted::class.java.name, InProgress::class.java.name -> NotStarted Error::class.java.name, InProgressAfterError::class.java.name -> Error Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int) else -> NotStarted } }, ) } } } interface Progress { Loading packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt +5 −1 Original line number Diff line number Diff line Loading @@ -131,10 +131,14 @@ private fun SuccessAnimation( ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation)) var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) } val progress by animateLottieCompositionAsState(composition, iterations = 1) if (progress == 1f) { animationFinished = true } LottieAnimation( composition = composition, progress = { progress }, progress = { if (animationFinished) 1f else progress }, dynamicProperties = animationProperties, modifier = Modifier.fillMaxSize(), ) Loading packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +9 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter Loading @@ -48,8 +51,13 @@ fun GestureTutorialScreen( onBack: () -> Unit, ) { BackHandler(onBack = onBack) var cachedTutorialState: TutorialActionState by rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { mutableStateOf(NotStarted) } val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false) val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted) val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState) cachedTutorialState = tutorialState TouchpadGesturesHandlingBox( motionEventConsumer, tutorialState, Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt 0 → 100644 +77 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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 * * http://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.android.systemui.touchpad.tutorial.ui.composable import androidx.compose.runtime.saveable.SaverScope import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class TutorialActionStateSaverTest : SysuiTestCase() { private val saver = TutorialActionState.stateSaver() private val saverScope: SaverScope = object : SaverScope { override fun canBeSaved(value: Any) = true } @Test fun inProgressIsRestoredToNotStartedState() { assertRestoredState( savedState = InProgress(progress = 0f), expectedRestoredState = NotStarted, ) } @Test fun inProgressErrorIsRestoredToErrorState() { assertRestoredState( savedState = InProgressAfterError(InProgress(progress = 0f)), expectedRestoredState = Error, ) } @Test fun otherStatesAreRestoredToTheSameState() { assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted) assertRestoredState(savedState = Error, expectedRestoredState = Error) assertRestoredState( savedState = Finished(successAnimation = R.raw.trackpad_home_success), expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success), ) } private fun assertRestoredState( savedState: TutorialActionState, expectedRestoredState: TutorialActionState, ) { val savedValue = with(saver) { saverScope.save(savedState) } assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState) } }
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt +5 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester Loading @@ -45,7 +46,10 @@ import com.android.systemui.res.R fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) { BackHandler(onBack = onBack) val screenConfig = buildScreenConfig() var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) } var actionState: TutorialActionState by rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { mutableStateOf(NotStarted) } val focusRequester = remember { FocusRequester() } Box( modifier = Loading
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt +27 −0 Original line number Diff line number Diff line Loading @@ -38,6 +38,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.mapSaver import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester Loading Loading @@ -69,6 +71,31 @@ sealed interface TutorialActionState { data class InProgressAfterError(val inProgress: InProgress) : TutorialActionState, Progress by inProgress companion object { fun stateSaver(): Saver<TutorialActionState, Any> { val classKey = "class" val successAnimationKey = "animation" return mapSaver( save = { buildMap { put(classKey, it::class.java.name) if (it is Finished) put(successAnimationKey, it.successAnimation) } }, restore = { map -> when (map[classKey] as? String) { NotStarted::class.java.name, InProgress::class.java.name -> NotStarted Error::class.java.name, InProgressAfterError::class.java.name -> Error Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int) else -> NotStarted } }, ) } } } interface Progress { Loading
packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt +5 −1 Original line number Diff line number Diff line Loading @@ -131,10 +131,14 @@ private fun SuccessAnimation( ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation)) var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) } val progress by animateLottieCompositionAsState(composition, iterations = 1) if (progress == 1f) { animationFinished = true } LottieAnimation( composition = composition, progress = { progress }, progress = { if (animationFinished) 1f else progress }, dynamicProperties = animationProperties, modifier = Modifier.fillMaxSize(), ) Loading
packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +9 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter Loading @@ -48,8 +51,13 @@ fun GestureTutorialScreen( onBack: () -> Unit, ) { BackHandler(onBack = onBack) var cachedTutorialState: TutorialActionState by rememberSaveable(stateSaver = TutorialActionState.stateSaver()) { mutableStateOf(NotStarted) } val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false) val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted) val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState) cachedTutorialState = tutorialState TouchpadGesturesHandlingBox( motionEventConsumer, tutorialState, Loading