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

Commit 2d7a8466 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Unfold transition support in the shade scene." into main

parents 9c827467 7320f0f6
Loading
Loading
Loading
Loading
+0 −77
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.fold.ui.composable

import androidx.annotation.FloatRange
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import com.android.compose.modifiers.padding
import kotlin.math.roundToInt

/**
 * Applies a translation that feeds off of the unfold transition that's active while the device is
 * being folded or unfolded, effectively shifting the element towards the fold hinge.
 *
 * @param startSide `true` if the affected element is on the start side (left-hand side in
 *   left-to-right layouts), `false` otherwise.
 * @param fullTranslation The maximum translation to apply when the element is the most shifted. The
 *   modifier will never apply more than this much translation on the element.
 * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
 *   be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
 */
@Composable
fun Modifier.unfoldTranslation(
    startSide: Boolean,
    fullTranslation: Dp,
    @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
): Modifier {
    val translateToTheRight = startSide && LocalLayoutDirection.current == LayoutDirection.Ltr
    return this.graphicsLayer {
        translationX =
            fullTranslation.toPx() *
                if (translateToTheRight) {
                    1 - unfoldProgress()
                } else {
                    unfoldProgress() - 1
                }
    }
}

/**
 * Applies horizontal padding that feeds off of the unfold transition that's active while the device
 * is being folded or unfolded, effectively "squishing" the element on both sides.
 *
 * This is horizontal padding so it's applied on both the start and end sides of the element.
 *
 * @param fullPadding The maximum padding to apply when the element is the most padded. The modifier
 *   will never apply more than this much horizontal padding on the element.
 * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
 *   be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
 */
@Composable
fun Modifier.unfoldHorizontalPadding(
    fullPadding: Dp,
    @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
): Modifier {
    return this.padding(
        horizontal = { (fullPadding.toPx() * (1 - unfoldProgress())).roundToInt() },
    )
}
+18 −22
Original line number Diff line number Diff line
@@ -62,11 +62,10 @@ import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.fold.ui.composable.unfoldHorizontalPadding
import com.android.systemui.fold.ui.composable.unfoldTranslation
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -291,7 +290,18 @@ private fun SceneScope.SplitShade(
        remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
    val tileSquishiness by
        animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
    val unfoldTransitionProgress by viewModel.unfoldTransitionProgress.collectAsState()
    val unfoldTranslationXForStartSide by
        viewModel
            .unfoldTranslationX(
                isOnStartSide = true,
            )
            .collectAsState(0f)
    val unfoldTranslationXForEndSide by
        viewModel
            .unfoldTranslationX(
                isOnStartSide = false,
            )
            .collectAsState(0f)

    val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
    val density = LocalDensity.current
@@ -340,21 +350,16 @@ private fun SceneScope.SplitShade(
                modifier =
                    Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
                        .then(brightnessMirrorShowingModifier)
                        .unfoldHorizontalPadding(
                            fullPadding = dimensionResource(R.dimen.notification_side_paddings),
                        ) {
                            unfoldTransitionProgress
                        }
                        .padding(
                            horizontal = { unfoldTranslationXForStartSide.roundToInt() },
                        )
            )

            Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
                Box(
                    modifier =
                        Modifier.weight(1f).unfoldTranslation(
                            startSide = true,
                            fullTranslation = dimensionResource(R.dimen.notification_side_paddings),
                        ) {
                            unfoldTransitionProgress
                        Modifier.weight(1f).graphicsLayer {
                            translationX = unfoldTranslationXForStartSide
                        },
                ) {
                    BrightnessMirror(
@@ -424,15 +429,6 @@ private fun SceneScope.SplitShade(
                            .fillMaxHeight()
                            .padding(bottom = navBarBottomHeight)
                            .then(brightnessMirrorShowingModifier)
                            .unfoldTranslation(
                                startSide = false,
                                fullTranslation =
                                    dimensionResource(
                                        R.dimen.notification_side_paddings,
                                    ),
                            ) {
                                unfoldTransitionProgress
                            },
                )
            }
        }
+42 −6
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -49,7 +50,9 @@ import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -261,22 +264,55 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
    @Test
    fun unfoldTransitionProgress() =
        testScope.runTest {
            val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
            val progress by collectLastValue(underTest.unfoldTransitionProgress)
            val maxTranslation = prepareConfiguration()
            val translations by
                collectLastValue(
                    combine(
                        underTest.unfoldTranslationX(isOnStartSide = true),
                        underTest.unfoldTranslationX(isOnStartSide = false),
                    ) { start, end ->
                        Translations(
                            start = start,
                            end = end,
                        )
                    }
                )

            val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
            unfoldProvider.onTransitionStarted()
            assertThat(progress).isEqualTo(1f)
            assertThat(translations?.start).isEqualTo(0f)
            assertThat(translations?.end).isEqualTo(-0f)

            repeat(10) { repetition ->
                val transitionProgress = 0.1f * (repetition + 1)
                unfoldProvider.onTransitionProgress(transitionProgress)
                assertThat(progress).isEqualTo(transitionProgress)
                assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
                assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
            }

            unfoldProvider.onTransitionFinishing()
            assertThat(progress).isEqualTo(1f)
            assertThat(translations?.start).isEqualTo(0f)
            assertThat(translations?.end).isEqualTo(-0f)

            unfoldProvider.onTransitionFinished()
            assertThat(progress).isEqualTo(1f)
            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
    }

    private data class Translations(
        val start: Float,
        val end: Float,
    )
}
+89 −7
Original line number Diff line number Diff line
@@ -18,13 +18,17 @@ package com.android.systemui.unfold.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -79,24 +83,102 @@ class UnfoldTransitionInteractorTest : SysuiTestCase() {
        }

    @Test
    fun unfoldProgress() =
    fun unfoldTranslationX_leftToRight() =
        testScope.runTest {
            val progress by collectLastValue(underTest.unfoldProgress)
            val maxTranslation = prepareConfiguration(isLeftToRight = true)
            val translations by
                collectLastValue(
                    combine(
                        underTest.unfoldTranslationX(isOnStartSide = true),
                        underTest.unfoldTranslationX(isOnStartSide = false),
                    ) { start, end ->
                        Translations(
                            start = start,
                            end = end,
                        )
                    }
                )
            runCurrent()

            unfoldTransitionProgressProvider.onTransitionStarted()
            assertThat(progress).isEqualTo(1f)
            assertThat(translations?.start).isEqualTo(0f)
            assertThat(translations?.end).isEqualTo(-0f)

            repeat(10) { repetition ->
                val transitionProgress = 0.1f * (repetition + 1)
                val transitionProgress = 1 - 0.1f * (repetition + 1)
                unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
                assertThat(progress).isEqualTo(transitionProgress)
                assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
                assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
            }

            unfoldTransitionProgressProvider.onTransitionFinishing()
            assertThat(progress).isEqualTo(1f)
            assertThat(translations?.start).isEqualTo(maxTranslation)
            assertThat(translations?.end).isEqualTo(-maxTranslation)

            unfoldTransitionProgressProvider.onTransitionFinished()
            assertThat(progress).isEqualTo(1f)
            assertThat(translations?.start).isEqualTo(0f)
            assertThat(translations?.end).isEqualTo(-0f)
        }

    @Test
    fun unfoldTranslationX_rightToLeft() =
        testScope.runTest {
            val maxTranslation = prepareConfiguration(isLeftToRight = false)
            val translations by
                collectLastValue(
                    combine(
                        underTest.unfoldTranslationX(isOnStartSide = true),
                        underTest.unfoldTranslationX(isOnStartSide = false),
                    ) { start, end ->
                        Translations(
                            start = start,
                            end = end,
                        )
                    }
                )
            runCurrent()

            unfoldTransitionProgressProvider.onTransitionStarted()
            assertThat(translations?.start).isEqualTo(-0f)
            assertThat(translations?.end).isEqualTo(0f)

            repeat(10) { repetition ->
                val transitionProgress = 1 - 0.1f * (repetition + 1)
                unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
                assertThat(translations?.start)
                    .isEqualTo(-(1 - transitionProgress) * maxTranslation)
                assertThat(translations?.end).isEqualTo((1 - transitionProgress) * maxTranslation)
            }

            unfoldTransitionProgressProvider.onTransitionFinishing()
            assertThat(translations?.start).isEqualTo(-maxTranslation)
            assertThat(translations?.end).isEqualTo(maxTranslation)

            unfoldTransitionProgressProvider.onTransitionFinished()
            assertThat(translations?.start).isEqualTo(-0f)
            assertThat(translations?.end).isEqualTo(0f)
        }

    private fun prepareConfiguration(
        isLeftToRight: Boolean,
    ): Int {
        val configuration = context.resources.configuration
        if (isLeftToRight) {
            configuration.setLayoutDirection(Locale.US)
        } else {
            configuration.setLayoutDirection(Locale("he", "il"))
        }
        kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
        val maxTranslation = 10
        kosmos.fakeConfigurationRepository.setDimensionPixelSize(
            R.dimen.notification_side_paddings,
            maxTranslation
        )
        return maxTranslation
    }

    private data class Translations(
        val start: Float,
        val end: Float,
    )
}
+7 −0
Original line number Diff line number Diff line
@@ -56,6 +56,13 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config
    val naturalMaxBounds: Flow<Rect> =
        repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()

    /**
     * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
     * `View#LAYOUT_DIRECTION_RTL`.
     */
    val layoutDirection: Flow<Int> =
        repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged()

    /** Given [resourceId], emit the dimension pixel size on config change */
    fun dimensionPixelSize(resourceId: Int): Flow<Int> {
        return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
Loading