Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +48 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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, ) } } packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -201,6 +202,7 @@ private fun NavBarAmbientCue( visible = visible, expanded = expanded, onDismiss = { viewModel.collapse() }, showEducation = viewModel.showLongPressEducation, modifier = modifier, padding = PaddingValues( Loading @@ -214,6 +216,7 @@ private fun NavBarAmbientCue( navBarWidth = navBarWidth, visible = visible, expanded = expanded, showEducation = viewModel.showFirstTimeEducation, modifier = modifier, onClick = { viewModel.expand() }, onCloseClick = { viewModel.hide() }, Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/FirstTimeEducation.kt 0 → 100644 +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, ) } } } packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/NavBarPill.kt +10 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -87,6 +88,7 @@ fun NavBarPill( modifier: Modifier = Modifier, visible: Boolean = true, expanded: Boolean = false, showEducation: Boolean = false, onClick: () -> Unit = {}, onCloseClick: () -> Unit = {}, ) { Loading Loading @@ -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 Loading @@ -173,8 +177,11 @@ fun NavBarPill( ) } } } }, ) { if (visible && !expanded && showEducation) { FirstTimeEducation(Alignment.CenterHorizontally) } Row( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, Loading @@ -190,7 +197,6 @@ fun NavBarPill( 1f } } .align(Alignment.BottomCenter) .padding(bottom = 4.dp), ) { val closeButtonSize = 28.dp Loading Loading @@ -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)) Loading packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/modifier/EduBalloon.kt 0 → 100644 +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
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/ActionList.kt +48 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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, ) } }
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/AmbientCueContainer.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -201,6 +202,7 @@ private fun NavBarAmbientCue( visible = visible, expanded = expanded, onDismiss = { viewModel.collapse() }, showEducation = viewModel.showLongPressEducation, modifier = modifier, padding = PaddingValues( Loading @@ -214,6 +216,7 @@ private fun NavBarAmbientCue( navBarWidth = navBarWidth, visible = visible, expanded = expanded, showEducation = viewModel.showFirstTimeEducation, modifier = modifier, onClick = { viewModel.expand() }, onCloseClick = { viewModel.hide() }, Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/FirstTimeEducation.kt 0 → 100644 +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, ) } } }
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/NavBarPill.kt +10 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -87,6 +88,7 @@ fun NavBarPill( modifier: Modifier = Modifier, visible: Boolean = true, expanded: Boolean = false, showEducation: Boolean = false, onClick: () -> Unit = {}, onCloseClick: () -> Unit = {}, ) { Loading Loading @@ -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 Loading @@ -173,8 +177,11 @@ fun NavBarPill( ) } } } }, ) { if (visible && !expanded && showEducation) { FirstTimeEducation(Alignment.CenterHorizontally) } Row( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, Loading @@ -190,7 +197,6 @@ fun NavBarPill( 1f } } .align(Alignment.BottomCenter) .padding(bottom = 4.dp), ) { val closeButtonSize = 28.dp Loading Loading @@ -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)) Loading
packages/SystemUI/compose/features/src/com/android/systemui/ambientcue/ui/compose/modifier/EduBalloon.kt 0 → 100644 +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) }