[flexiglass] Unfold transition support in the lockscreen scene.
When a foldable is fully unfolded and then the user begins to fold it
up, there's a subtle animation that happens to elements across the
screen. Left-hand side elements move to the right and right-hand side
elements move to the left, seeming to gently float towards the fold
hinge.
This CL adds that for Flexiglass, only for the split lockscreen scene.
Test: added integration test for the new code that exposes the
unfoldTranslations in the view-model
Test: manually verified that gently folding up the device correctly
slides the elements of the split lockscreen scene into the center (both left-hand
side elements and the NSSL on the right)
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Bug: 330483283
Change-Id: I56fd548c8cd46f33094de6058c2fa2830dcce005
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 28e92aa..e499c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -26,9 +26,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ }
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -127,8 +146,18 @@
// Aligned to bottom and NOT constrained by the lock icon.
with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
+ Shortcut(
+ isStart = true,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ Shortcut(
+ isStart = false,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
}
with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index b8f00dc..9d31955 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -26,9 +26,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ },
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -111,12 +130,26 @@
}
// Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = true,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ }
with(lockSection) { LockIcon() }
// Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = false,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
+ }
// Aligned to bottom and constrained to below the lock icon.
Column(modifier = Modifier.fillMaxWidth()) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index e9a8257..3497183 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,17 +21,21 @@
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -137,4 +141,47 @@
.isFalse()
}
}
+
+ @Test
+ fun unfoldTranslations() =
+ with(kosmos) {
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by collectLastValue(underTest.unfoldTranslations)
+
+ val unfoldProvider = fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start)
+ .isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end)
+ .isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+ }
+
+ private fun prepareConfiguration(): Int {
+ val configuration = context.resources.configuration
+ configuration.setLayoutDirection(Locale.US)
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation,
+ )
+ return maxTranslation
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 36896f9..ecad148 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +47,7 @@
val longPress: KeyguardLongPressViewModel,
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
private val clockSize = clockInteractor.clockSize
@@ -75,6 +77,23 @@
initialValue = false,
)
+ /** Amount of horizontal translation that should be applied to elements in the scene. */
+ val unfoldTranslations: StateFlow<UnfoldTranslations> =
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ UnfoldTranslations(
+ start = start,
+ end = end,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = UnfoldTranslations(),
+ )
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (isLargeClockVisible) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
@@ -94,4 +113,20 @@
initialValue = interactor.getCurrentBlueprint().id,
)
}
+
+ data class UnfoldTranslations(
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the start side
+ * (left in left-to-right layouts). Can also be used as horizontal padding for elements that
+ * need horizontal padding on both side. In pixels.
+ */
+ val start: Float = 0f,
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the end side
+ * (right in left-to-right layouts). In pixels.
+ */
+ val end: Float = 0f,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 1e25f7f..30a4f21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
val Kosmos.lockscreenContentViewModel by
Kosmos.Fixture {
@@ -32,5 +33,6 @@
longPress = keyguardLongPressViewModel,
shadeInteractor = shadeInteractor,
applicationScope = applicationCoroutineScope,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}