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

Commit 8aa95a15 authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Education tooltip

Bug: 418066889
Flag: com.android.systemui.enable_underlay
Test: atest AmbientCueViewModelTest
Test: atest NavBarPillScreenshotTest
Test: atest ActionListScreenshotTest
Change-Id: I34d8645e4c5f72f405ff86d1d4553edf8d9ca379
parent 42ebb848
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -21,9 +21,15 @@ import android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK
import android.os.VibrationEffect.Composition.PRIMITIVE_THUD
import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
import android.os.Vibrator
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation
@@ -36,8 +42,11 @@ import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
@@ -53,6 +62,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.graphicsLayer
@@ -60,8 +70,12 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.systemui.ambientcue.ui.compose.modifier.eduBalloon
import com.android.systemui.ambientcue.ui.viewmodel.ActionViewModel
import com.android.systemui.res.R
import kotlin.math.abs
import kotlin.math.max
import kotlinx.coroutines.flow.drop
@@ -72,6 +86,7 @@ fun ActionList(
    visible: Boolean,
    expanded: Boolean,
    onDismiss: () -> Unit,
    showEducation: Boolean = false,
    modifier: Modifier = Modifier,
    padding: PaddingValues = PaddingValues(0.dp),
    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
@@ -273,6 +288,16 @@ fun ActionList(
        verticalArrangement = Arrangement.spacedBy(columnSpacing, Alignment.Bottom),
        horizontalAlignment = horizontalAlignment,
    ) {
        if (showEducation && expanded) {
            AnimatedVisibility(
                visible = progress == 1f,
                enter = fadeIn() + scaleIn(),
                exit = fadeOut() + scaleOut(),
            ) {
                EducationTooltip(horizontalAlignment)
            }
        }

        val childHeights = remember(actions) { MutableList(actions.size) { 0 } }
        actions.forEachIndexed { index, action ->
            val scale by
@@ -317,3 +342,26 @@ fun ActionList(
        }
    }
}

@Composable
private fun EducationTooltip(horizontalAlignment: Alignment.Horizontal) {
    val backgroundColor = MaterialTheme.colorScheme.tertiary
    val foregroundColor = MaterialTheme.colorScheme.onTertiary
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(6.dp),
        modifier = Modifier.eduBalloon(backgroundColor, horizontalAlignment),
    ) {
        Image(
            painter = painterResource(R.drawable.ic_ambientcue_hold_tooltip),
            contentDescription = null,
            colorFilter = ColorFilter.tint(foregroundColor),
            modifier = Modifier.size(32.dp),
        )
        Text(
            text = stringResource(R.string.ambientcue_long_press_edu_text),
            style = MaterialTheme.typography.labelLarge,
            color = foregroundColor,
        )
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -131,6 +131,7 @@ private fun TaskBarAnd3ButtonAmbientCue(
        expanded = expanded,
        horizontalAlignment = Alignment.End,
        onDismiss = { viewModel.collapse() },
        showEducation = viewModel.showLongPressEducation,
        modifier =
            modifier.graphicsLayer {
                translationX = screenWidthPx - size.width
@@ -201,6 +202,7 @@ private fun NavBarAmbientCue(
        visible = visible,
        expanded = expanded,
        onDismiss = { viewModel.collapse() },
        showEducation = viewModel.showLongPressEducation,
        modifier = modifier,
        padding =
            PaddingValues(
@@ -214,6 +216,7 @@ private fun NavBarAmbientCue(
        navBarWidth = navBarWidth,
        visible = visible,
        expanded = expanded,
        showEducation = viewModel.showFirstTimeEducation,
        modifier = modifier,
        onClick = { viewModel.expand() },
        onCloseClick = { viewModel.hide() },
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.ambientcue.ui.compose

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.systemui.ambientcue.ui.compose.modifier.eduBalloon
import com.android.systemui.res.R

@Composable
fun FirstTimeEducation(horizontalAlignment: Alignment.Horizontal, modifier: Modifier = Modifier) {
    val backgroundColor = MaterialTheme.colorScheme.surfaceBright
    Row(
        verticalAlignment = Alignment.Top,
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier.eduBalloon(backgroundColor, horizontalAlignment),
    ) {
        Image(
            painter = painterResource(R.drawable.ic_ambientcue_edu),
            contentDescription =
                stringResource(R.string.ambientcue_first_time_edu_icon_description),
            modifier = Modifier.size(56.dp),
        )
        Column {
            Text(
                text = stringResource(R.string.ambientcue_first_time_edu_title),
                style = MaterialTheme.typography.titleMedium,
                color = MaterialTheme.colorScheme.onPrimaryContainer,
            )
            Text(
                text = stringResource(R.string.ambientcue_first_time_edu_text),
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
            )
        }
    }
}
+10 −3
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -87,6 +88,7 @@ fun NavBarPill(
    modifier: Modifier = Modifier,
    visible: Boolean = true,
    expanded: Boolean = false,
    showEducation: Boolean = false,
    onClick: () -> Unit = {},
    onCloseClick: () -> Unit = {},
) {
@@ -151,7 +153,9 @@ fun NavBarPill(
            fontWeight = if (isBoldTextEnabled) FontWeight.Bold else FontWeight.Medium
        )

    Box(
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Bottom,
        modifier =
            modifier.defaultMinSize(minWidth = 412.dp, minHeight = 50.dp).drawBehind {
                // SmartScrim
@@ -173,8 +177,11 @@ fun NavBarPill(
                        )
                    }
                }
            }
            },
    ) {
        if (visible && !expanded && showEducation) {
            FirstTimeEducation(Alignment.CenterHorizontally)
        }
        Row(
            horizontalArrangement = Arrangement.spacedBy(6.dp),
            verticalAlignment = Alignment.CenterVertically,
@@ -190,7 +197,6 @@ fun NavBarPill(
                                1f
                            }
                    }
                    .align(Alignment.BottomCenter)
                    .padding(bottom = 4.dp),
        ) {
            val closeButtonSize = 28.dp
@@ -244,6 +250,7 @@ fun NavBarPill(
                                // Expanded chip for single action or MR
                                Row(
                                    horizontalArrangement = Arrangement.spacedBy(6.dp),
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier =
                                        Modifier.padding(end = 3.dp)
                                            .clip(RoundedCornerShape(16.dp))
+68 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.ambientcue.ui.compose.modifier

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.unit.dp

@Composable
fun Modifier.eduBalloon(
    backgroundColor: Color,
    horizontalAlignment: Alignment.Horizontal,
): Modifier {
    return this.padding(bottom = 12.dp)
        .drawBehind {
            val arrowPosition = with(density) { 48.dp.toPx() }
            val oneDp = with(density) { 1.dp.toPx() }
            val translationX = with(density) { 6.dp.toPx() }
            val translationY = with(density) { 10.dp.toPx() }

            val center =
                when (horizontalAlignment) {
                    Alignment.Start -> arrowPosition
                    Alignment.End -> size.width - arrowPosition
                    else -> size.width / 2f
                }

            translate(center, size.height - oneDp) {
                drawPath(
                    path =
                        Path().apply {
                            moveTo(-translationX, 0f)
                            lineTo(0f, translationY)
                            lineTo(translationX, 0f)
                        },
                    color = backgroundColor,
                )
            }
        }
        .clip(RoundedCornerShape(28.dp))
        .widthIn(max = 296.dp)
        .background(backgroundColor)
        .padding(16.dp)
}
Loading