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

Commit 1253632d authored by William Leshner's avatar William Leshner Committed by Android (Google) Code Review
Browse files

Merge "Animate the "go to dream" button tooltip." into main

parents 7272c960 98278c5d
Loading
Loading
Loading
Loading
+58 −5
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@

package com.android.systemui.communal.ui.compose.section

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
@@ -28,6 +34,11 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
@@ -52,6 +63,8 @@ import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay

class CommunalToDreamButtonSection
@Inject
@@ -75,16 +88,54 @@ constructor(
        val buttonSize = dimensionResource(R.dimen.communal_to_dream_button_size)

        if (viewModel.shouldShowTooltip) {
            val tooltipVisibleState = remember { MutableTransitionState(false) }

            Column(
                modifier =
                    Modifier.widthIn(max = tooltipMaxWidth).pointerInput(Unit) {
                        observeTaps { viewModel.setDreamButtonTooltipDismissed() }
                        observeTaps {
                            if (tooltipVisibleState.isCurrentlyVisible()) {
                                tooltipVisibleState.targetState = false
                            }
                        }
                    }
            ) {
                var waitingToShowTooltip by remember { mutableStateOf(true) }

                LaunchedEffect(tooltipVisibleState.targetState) {
                    delay(3.seconds)
                    tooltipVisibleState.targetState = true
                    waitingToShowTooltip = false
                }

                // This LaunchedEffect is used to wait for the tooltip dismiss animation to
                // complete before setting the tooltip dismissed. Otherwise, the composable would
                // be removed before the animation can start.
                LaunchedEffect(
                    tooltipVisibleState.currentState,
                    tooltipVisibleState.isIdle,
                    waitingToShowTooltip,
                ) {
                    if (
                        !waitingToShowTooltip &&
                            !tooltipVisibleState.currentState &&
                            tooltipVisibleState.isIdle
                    ) {
                        viewModel.setDreamButtonTooltipDismissed()
                    }
                }

                AnimatedVisibility(
                    visibleState = tooltipVisibleState,
                    enter = fadeIn() + expandVertically(expandFrom = Alignment.Bottom),
                    exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Bottom),
                ) {
                    Tooltip(
                        pointerOffsetDp = buttonSize.div(2),
                        text = stringResource(R.string.glanceable_hub_to_dream_button_tooltip),
                    )
                }

                GoToDreamButton(
                    modifier = Modifier.width(buttonSize).height(buttonSize).align(Alignment.End)
                ) {
@@ -98,6 +149,8 @@ constructor(
        }
    }

    private fun MutableTransitionState<Boolean>.isCurrentlyVisible() = currentState && isIdle

    companion object {
        private val tooltipMaxWidth = 350.dp
    }
+11 −1
Original line number Diff line number Diff line
@@ -126,12 +126,22 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
        }

    @Test
    fun shouldShowDreamButtonTooltip_trueWhenNotDismissed() =
    fun shouldShowDreamButtonTooltip_trueWhenNotDismissedAndHubOnboardingDismissed() =
        kosmos.runTest {
            setSelectedUser(MAIN_USER)
            fakeCommunalPrefsRepository.setHubOnboardingDismissed(MAIN_USER)
            runCurrent()

            assertThat(underTest.shouldShowTooltip).isTrue()
        }

    @Test
    fun shouldShowDreamButtonTooltip_falseWhenNotDismissedAndHubOnboardingNotDismissed() =
        kosmos.runTest {
            runCurrent()
            assertThat(underTest.shouldShowTooltip).isFalse()
        }

    @Test
    fun shouldShowDreamButtonTooltip_falseWhenDismissed() =
        kosmos.runTest {
+8 −3
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.isDevicePluggedIn
import com.android.systemui.util.kotlin.sample
import dagger.assisted.AssistedFactory
@@ -40,7 +42,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -68,12 +69,16 @@ constructor(
            source = batteryController.isDevicePluggedIn().distinctUntilChanged(),
        )

    /** Return whether the dream button tooltip has been dismissed. */
    /** Return whether to show the dream button tooltip. */
    val shouldShowTooltip: Boolean by
        hydrator.hydratedStateOf(
            traceName = "shouldShowTooltip",
            initialValue = false,
            source = prefsInteractor.isDreamButtonTooltipDismissed.map { !it },
            source =
                allOf(
                    not(prefsInteractor.isDreamButtonTooltipDismissed),
                    prefsInteractor.isHubOnboardingDismissed,
                ),
        )

    /** Set the dream button tooltip to be dismissed. */