Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt +58 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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) ) { Loading @@ -98,6 +149,8 @@ constructor( } } private fun MutableTransitionState<Boolean>.isCurrentlyVisible() = currentState && isIdle companion object { private val tooltipMaxWidth = 350.dp } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +11 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt +8 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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. */ Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt +58 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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) ) { Loading @@ -98,6 +149,8 @@ constructor( } } private fun MutableTransitionState<Boolean>.isCurrentlyVisible() = currentState && isIdle companion object { private val tooltipMaxWidth = 350.dp } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +11 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt +8 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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. */ Loading