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

Commit a9b4bc5b authored by Will Leshner's avatar Will Leshner
Browse files

Add an education tooltip to "go to dream" button on hub.

Bug: 392629921
Test: atest CommunalPrefsRepositoryImplTest CommunalPrefsInteractorTest CommunalToDreamButtonViewModelTest
Flag: com.android.systemui.glanceable_hub_v2

Change-Id: I72d9d70f7ce113625bf3508e129d1f9646d7a909
parent df02053b
Loading
Loading
Loading
Loading
+5 −12
Original line number Original line Diff line number Diff line
@@ -156,14 +156,8 @@ constructor(


                val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
                val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)


                val screensaverButtonSizeInt = screensaverButtonSize.roundToPx()
                val screensaverButtonPlaceable =
                val screensaverButtonPlaceable =
                    screensaverButtonMeasurable?.measure(
                    screensaverButtonMeasurable?.measure(noMinConstraints)
                        Constraints.fixed(
                            width = screensaverButtonSizeInt,
                            height = screensaverButtonSizeInt,
                        )
                    )


                val communalGridPlaceable =
                val communalGridPlaceable =
                    communalGridMeasurable.measure(
                    communalGridMeasurable.measure(
@@ -181,12 +175,12 @@ constructor(
                    screensaverButtonPlaceable?.place(
                    screensaverButtonPlaceable?.place(
                        x =
                        x =
                            constraints.maxWidth -
                            constraints.maxWidth -
                                screensaverButtonSizeInt -
                                screensaverButtonPaddingInt -
                                screensaverButtonPaddingInt,
                                screensaverButtonPlaceable.width,
                        y =
                        y =
                            constraints.maxHeight -
                            constraints.maxHeight -
                                screensaverButtonSizeInt -
                                screensaverButtonPaddingInt -
                                screensaverButtonPaddingInt,
                                screensaverButtonPlaceable.height,
                    )
                    )
                }
                }
            }
            }
@@ -194,7 +188,6 @@ constructor(
    }
    }


    companion object {
    companion object {
        private val screensaverButtonSize: Dp = 64.dp
        private val screensaverButtonPadding: Dp = 24.dp
        private val screensaverButtonPadding: Dp = 24.dp


        // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
        // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area
+126 −15
Original line number Original line Diff line number Diff line
@@ -16,14 +16,37 @@


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


import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformIconButton
import com.android.compose.PlatformIconButton
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.ui.compose.extensions.observeTaps
import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.res.R
@@ -43,18 +66,48 @@ constructor(


        val viewModel =
        val viewModel =
            rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() }
            rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() }
        val shouldShowDreamButtonOnHub by
            viewModel.shouldShowDreamButtonOnHub.collectAsStateWithLifecycle(false)


        if (!shouldShowDreamButtonOnHub) {
        if (!viewModel.shouldShowDreamButtonOnHub) {
            return
            return
        }
        }


        if (viewModel.shouldShowTooltip) {
            Column(
                modifier =
                    Modifier.widthIn(max = tooltipMaxWidth).pointerInput(Unit) {
                        observeTaps { viewModel.setDreamButtonTooltipDismissed() }
                    }
            ) {
                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)
                ) {
                    viewModel.onShowDreamButtonTap()
                }
            }
        } else {
            GoToDreamButton(modifier = Modifier.width(buttonSize).height(buttonSize)) {
                viewModel.onShowDreamButtonTap()
            }
        }
    }

    companion object {
        private val buttonSize = 64.dp
        private val tooltipMaxWidth = 350.dp
    }
}

@Composable
private fun GoToDreamButton(modifier: Modifier, onClick: () -> Unit) {
    PlatformIconButton(
    PlatformIconButton(
            onClick = { viewModel.onShowDreamButtonTap() },
        modifier = modifier,
        onClick = onClick,
        iconResource = R.drawable.ic_screensaver_auto,
        iconResource = R.drawable.ic_screensaver_auto,
            contentDescription =
        contentDescription = stringResource(R.string.accessibility_glanceable_hub_to_dream_button),
                stringResource(R.string.accessibility_glanceable_hub_to_dream_button),
        colors =
        colors =
            IconButtonDefaults.filledIconButtonColors(
            IconButtonDefaults.filledIconButtonColors(
                contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
                contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -62,4 +115,62 @@ constructor(
            ),
            ),
    )
    )
}
}

@Composable
private fun Tooltip(pointerOffsetDp: Dp, text: String) {
    Surface(
        color = MaterialTheme.colorScheme.surface,
        shape = TooltipShape(pointerSizeDp = 12.dp, pointerOffsetDp = pointerOffsetDp),
    ) {
        Text(
            modifier = Modifier.padding(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp),
            color = MaterialTheme.colorScheme.onSurface,
            text = text,
        )
    }

    Spacer(modifier = Modifier.height(4.dp))
}

private class TooltipShape(private val pointerSizeDp: Dp, private val pointerOffsetDp: Dp) : Shape {

    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density,
    ): Outline {

        val pointerSizePx = with(density) { pointerSizeDp.toPx() }
        val pointerOffsetPx = with(density) { pointerOffsetDp.toPx() }
        val cornerRadius = CornerRadius(CornerSize(16.dp).toPx(size, density))
        val bubbleSize = size.copy(height = size.height - pointerSizePx)

        val path =
            Path().apply {
                addRoundRect(
                    RoundRect(
                        rect = bubbleSize.toRect(),
                        topLeft = cornerRadius,
                        topRight = cornerRadius,
                        bottomRight = cornerRadius,
                        bottomLeft = cornerRadius,
                    )
                )
                addPath(
                    Path().apply {
                        moveTo(0f, 0f)
                        lineTo(pointerSizePx / 2f, pointerSizePx)
                        lineTo(pointerSizePx, 0f)
                        close()
                    },
                    offset =
                        Offset(
                            x = bubbleSize.width - pointerOffsetPx - pointerSizePx / 2f,
                            y = bubbleSize.height,
                        ),
                )
            }

        return Outline.Generic(path)
    }
}
}
+28 −0
Original line number Original line Diff line number Diff line
@@ -115,6 +115,34 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
            assertThat(isHubOnboardingDismissed).isFalse()
            assertThat(isHubOnboardingDismissed).isFalse()
        }
        }


    @Test
    fun isDreamButtonTooltipDismissedValue_byDefault_isFalse() =
        testScope.runTest {
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))
            assertThat(isDreamButtonTooltipDismissed).isFalse()
        }

    @Test
    fun isDreamButtonTooltipDismissedValue_onSet_isTrue() =
        testScope.runTest {
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))

            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
            assertThat(isDreamButtonTooltipDismissed).isTrue()
        }

    @Test
    fun isDreamButtonTooltipDismissedValue_onSetForDifferentUser_isStillFalse() =
        testScope.runTest {
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER))

            underTest.setDreamButtonTooltipDismissed(SECONDARY_USER)
            assertThat(isDreamButtonTooltipDismissed).isFalse()
        }

    @Test
    @Test
    fun getSharedPreferences_whenFileRestored() =
    fun getSharedPreferences_whenFileRestored() =
        testScope.runTest {
        testScope.runTest {
+37 −0
Original line number Original line Diff line number Diff line
@@ -108,6 +108,43 @@ class CommunalPrefsInteractorTest : SysuiTestCase() {
            assertThat(isHubOnboardingDismissed).isFalse()
            assertThat(isHubOnboardingDismissed).isFalse()
        }
        }


    @Test
    fun setDreamButtonTooltipDismissed_currentUser() =
        testScope.runTest {
            setSelectedUser(MAIN_USER)
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed)

            assertThat(isDreamButtonTooltipDismissed).isFalse()
            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
            assertThat(isDreamButtonTooltipDismissed).isTrue()
        }

    @Test
    fun setDreamButtonTooltipDismissed_anotherUser() =
        testScope.runTest {
            setSelectedUser(MAIN_USER)
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed)

            assertThat(isDreamButtonTooltipDismissed).isFalse()
            underTest.setDreamButtonTooltipDismissed(SECONDARY_USER)
            assertThat(isDreamButtonTooltipDismissed).isFalse()
        }

    @Test
    fun isDreamButtonTooltipDismissed_userSwitch() =
        testScope.runTest {
            setSelectedUser(MAIN_USER)
            underTest.setDreamButtonTooltipDismissed(MAIN_USER)
            val isDreamButtonTooltipDismissed by
                collectLastValue(underTest.isDreamButtonTooltipDismissed)

            assertThat(isDreamButtonTooltipDismissed).isTrue()
            setSelectedUser(SECONDARY_USER)
            assertThat(isDreamButtonTooltipDismissed).isFalse()
        }

    private suspend fun setSelectedUser(user: UserInfo) {
    private suspend fun setSelectedUser(user: UserInfo) {
        with(kosmos.fakeUserRepository) {
        with(kosmos.fakeUserRepository) {
            setUserInfos(listOf(user))
            setUserInfos(listOf(user))
+33 −6
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.systemui.communal.ui.viewmodel
package com.android.systemui.communal.ui.viewmodel


import android.content.pm.UserInfo
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings
import android.service.dream.dreamManager
import android.service.dream.dreamManager
@@ -24,15 +25,17 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.uiEventLoggerFake
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
import com.android.systemui.communal.domain.interactor.HubOnboardingInteractorTest.Companion.MAIN_USER
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.plugins.activityStarter
import com.android.systemui.plugins.activityStarter
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.testKosmos
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -52,7 +55,6 @@ import org.mockito.kotlin.whenever
class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val testScope = kosmos.testScope
    private val uiEventLoggerFake = kosmos.uiEventLoggerFake
    private val underTest: CommunalToDreamButtonViewModel by lazy {
    private val underTest: CommunalToDreamButtonViewModel by lazy {
        kosmos.communalToDreamButtonViewModel
        kosmos.communalToDreamButtonViewModel
    }
    }
@@ -68,9 +70,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
        with(kosmos) {
        with(kosmos) {
            runTest {
            runTest {
                whenever(batteryController.isPluggedIn()).thenReturn(true)
                whenever(batteryController.isPluggedIn()).thenReturn(true)
                runCurrent()


                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
                assertThat(underTest.shouldShowDreamButtonOnHub).isTrue()
                assertThat(shouldShowButton).isTrue()
            }
            }
        }
        }


@@ -79,9 +81,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
        with(kosmos) {
        with(kosmos) {
            runTest {
            runTest {
                whenever(batteryController.isPluggedIn()).thenReturn(false)
                whenever(batteryController.isPluggedIn()).thenReturn(false)
                runCurrent()


                val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub)
                assertThat(underTest.shouldShowDreamButtonOnHub).isFalse()
                assertThat(shouldShowButton).isFalse()
            }
            }
        }
        }


@@ -123,6 +125,23 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
            }
            }
        }
        }


    @Test
    fun shouldShowDreamButtonTooltip_trueWhenNotDismissed() =
        kosmos.runTest {
            runCurrent()
            assertThat(underTest.shouldShowTooltip).isTrue()
        }

    @Test
    fun shouldShowDreamButtonTooltip_falseWhenDismissed() =
        kosmos.runTest {
            setSelectedUser(MAIN_USER)
            fakeCommunalPrefsRepository.setDreamButtonTooltipDismissed(MAIN_USER)
            runCurrent()

            assertThat(underTest.shouldShowTooltip).isFalse()
        }

    @Test
    @Test
    fun onShowDreamButtonTap_eventLogged() =
    fun onShowDreamButtonTap_eventLogged() =
        with(kosmos) {
        with(kosmos) {
@@ -134,4 +153,12 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() {
                    .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP.id)
                    .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP.id)
            }
            }
        }
        }

    private suspend fun setSelectedUser(user: UserInfo) {
        with(kosmos.fakeUserRepository) {
            setUserInfos(listOf(user))
            setSelectedUserInfo(user)
        }
        kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0)
    }
}
}
Loading