Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f03e716b authored by Alejandro Nijamkin's avatar Alejandro Nijamkin Committed by Ale Nijamkin
Browse files

[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
parent ade00290
Loading
Loading
Loading
Loading
+33 −4
Original line number Original line Diff line number Diff line
@@ -26,9 +26,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
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.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@ import dagger.Module
import dagger.multibindings.IntoSet
import dagger.multibindings.IntoSet
import java.util.Optional
import java.util.Optional
import javax.inject.Inject
import javax.inject.Inject
import kotlin.math.roundToInt


/**
/**
 * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
 * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@ constructor(
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val shouldUseSplitNotificationShade by
        val shouldUseSplitNotificationShade by
            viewModel.shouldUseSplitNotificationShade.collectAsState()
            viewModel.shouldUseSplitNotificationShade.collectAsState()
        val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()


        LockscreenLongPress(
        LockscreenLongPress(
            viewModel = viewModel.longPress,
            viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@ constructor(
                    Column(
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        modifier = Modifier.fillMaxSize(),
                    ) {
                    ) {
                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                        with(statusBarSection) {
                            StatusBar(
                                modifier =
                                    Modifier.fillMaxWidth()
                                        .padding(
                                            horizontal = { unfoldTranslations.start.roundToInt() },
                                        )
                            )
                        }


                        Box {
                        Box {
                            with(topAreaSection) { DefaultClockLayout() }
                            with(topAreaSection) {
                                DefaultClockLayout(
                                    modifier =
                                        Modifier.graphicsLayer {
                                            translationX = unfoldTranslations.start
                                        }
                                )
                            }
                            if (shouldUseSplitNotificationShade) {
                            if (shouldUseSplitNotificationShade) {
                                with(notificationSection) {
                                with(notificationSection) {
                                    Notifications(
                                    Notifications(
@@ -127,8 +146,18 @@ constructor(


                    // Aligned to bottom and NOT constrained by the lock icon.
                    // Aligned to bottom and NOT constrained by the lock icon.
                    with(bottomAreaSection) {
                    with(bottomAreaSection) {
                        Shortcut(isStart = true, applyPadding = true)
                        Shortcut(
                        Shortcut(isStart = false, applyPadding = true)
                            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) }
                    with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
                },
                },
+37 −4
Original line number Original line Diff line number Diff line
@@ -26,9 +26,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
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.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@ import dagger.Module
import dagger.multibindings.IntoSet
import dagger.multibindings.IntoSet
import java.util.Optional
import java.util.Optional
import javax.inject.Inject
import javax.inject.Inject
import kotlin.math.roundToInt


/**
/**
 * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
 * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@ constructor(
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val shouldUseSplitNotificationShade by
        val shouldUseSplitNotificationShade by
            viewModel.shouldUseSplitNotificationShade.collectAsState()
            viewModel.shouldUseSplitNotificationShade.collectAsState()
        val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()


        LockscreenLongPress(
        LockscreenLongPress(
            viewModel = viewModel.longPress,
            viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@ constructor(
                    Column(
                    Column(
                        modifier = Modifier.fillMaxSize(),
                        modifier = Modifier.fillMaxSize(),
                    ) {
                    ) {
                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                        with(statusBarSection) {
                            StatusBar(
                                modifier =
                                    Modifier.fillMaxWidth()
                                        .padding(
                                            horizontal = { unfoldTranslations.start.roundToInt() },
                                        )
                            )
                        }


                        Box {
                        Box {
                            with(topAreaSection) { DefaultClockLayout() }
                            with(topAreaSection) {
                                DefaultClockLayout(
                                    modifier =
                                        Modifier.graphicsLayer {
                                            translationX = unfoldTranslations.start
                                        },
                                )
                            }
                            if (shouldUseSplitNotificationShade) {
                            if (shouldUseSplitNotificationShade) {
                                with(notificationSection) {
                                with(notificationSection) {
                                    Notifications(
                                    Notifications(
@@ -111,12 +130,26 @@ constructor(
                    }
                    }


                    // Constrained to the left of the lock icon (in left-to-right layouts).
                    // 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() }
                    with(lockSection) { LockIcon() }


                    // Constrained to the right of the lock icon (in left-to-right layouts).
                    // 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.
                    // Aligned to bottom and constrained to below the lock icon.
                    Column(modifier = Modifier.fillMaxWidth()) {
                    Column(modifier = Modifier.fillMaxWidth()) {
+47 −0
Original line number Original line Diff line number Diff line
@@ -21,17 +21,21 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardClockSwitch
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
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.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
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.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
@@ -137,4 +141,47 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
                    .isFalse()
                    .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
    }
}
}
+35 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +47,7 @@ constructor(
    val longPress: KeyguardLongPressViewModel,
    val longPress: KeyguardLongPressViewModel,
    val shadeInteractor: ShadeInteractor,
    val shadeInteractor: ShadeInteractor,
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
) {
    private val clockSize = clockInteractor.clockSize
    private val clockSize = clockInteractor.clockSize


@@ -75,6 +77,23 @@ constructor(
                initialValue = false,
                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 {
    fun getSmartSpacePaddingTop(resources: Resources): Int {
        return if (isLargeClockVisible) {
        return if (isLargeClockVisible) {
            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
            resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
@@ -94,4 +113,20 @@ constructor(
                initialValue = interactor.getCurrentBlueprint().id,
                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,
    )
}
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor


val Kosmos.lockscreenContentViewModel by
val Kosmos.lockscreenContentViewModel by
    Kosmos.Fixture {
    Kosmos.Fixture {
@@ -32,5 +33,6 @@ val Kosmos.lockscreenContentViewModel by
            longPress = keyguardLongPressViewModel,
            longPress = keyguardLongPressViewModel,
            shadeInteractor = shadeInteractor,
            shadeInteractor = shadeInteractor,
            applicationScope = applicationCoroutineScope,
            applicationScope = applicationCoroutineScope,
            unfoldTransitionInteractor = unfoldTransitionInteractor,
        )
        )
    }
    }