Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +69 −63 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection Loading @@ -44,14 +45,17 @@ constructor( private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, private val ambientStatusBarSection: AmbientStatusBarSection, private val communalPopupSection: CommunalPopupSection, ) { @Composable fun SceneScope.Content(modifier: Modifier = Modifier) { CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) { Layout( modifier = modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(), content = { Box(modifier = Modifier.fillMaxSize()) { with(communalPopupSection) { Popup() } with(ambientStatusBarSection) { AmbientStatusBar(modifier = Modifier.fillMaxWidth()) } Loading Loading @@ -97,7 +101,8 @@ constructor( val bottomAreaPlaceable = bottomAreaMeasurable.measure( noMinConstraints.copy( maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) ) ) Loading @@ -123,3 +128,4 @@ constructor( } } } } packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +19 −167 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.widget.FrameLayout import android.widget.RemoteViews import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState Loading Loading @@ -71,7 +70,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets import androidx.compose.material3.Button import androidx.compose.material3.ButtonColors Loading Loading @@ -103,10 +101,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading @@ -126,13 +121,11 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.animation.Easings.Emphasized Loading @@ -151,7 +144,6 @@ import com.android.systemui.communal.ui.compose.extensions.observeTaps import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.PopupType import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R Loading @@ -171,7 +163,6 @@ fun CommunalHub( ) { val communalContent by viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } Loading Loading @@ -225,31 +216,17 @@ fun CommunalHub( gridCoordinates?.let { offset - it.positionInWindow() - contentOffset } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } // Display the button only when the gesture initiates from widgets, // the CTA tile, or an empty area on the screen. UMO/smartspace have // their own long-press handlers. To prevent user confusion, we // should // not display this button. if ( index == null || communalContent[index].isWidgetContent() || communalContent[index] is CommunalContentModel.CtaTileInViewMode ) { viewModel.onShowCustomizeWidgetButton() } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } // Handle long-click on widgets and set the selected index // correctly. We only handle widgets here because long click on // empty spaces is handled by CommunalPopupSection. if (key != null) { viewModel.onLongClick() viewModel.setSelectedKey(key) } } .onPreviewKeyEvent { onKeyEvent(viewModel) false } .motionEventSpy { onMotionEvent(viewModel) } }, ) { AccessibilityContainer(viewModel) { Loading Loading @@ -342,22 +319,6 @@ fun CommunalHub( ) } } if (currentPopup == PopupType.CtaTile) { PopupOnDismissCtaTile(viewModel::onHidePopup) } AnimatedVisibility( visible = currentPopup == PopupType.CustomizeWidgetButton, modifier = Modifier.fillMaxSize() ) { ButtonToEditWidgets( onClick = { viewModel.onHidePopup() viewModel.onOpenWidgetEditor(selectedKey.value) }, onHide = { viewModel.onHidePopup() } ) } if (viewModel is CommunalViewModel && dialogFactory != null) { val isEnableWidgetDialogShowing by Loading Loading @@ -413,14 +374,6 @@ fun CommunalHub( } } private fun onKeyEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } private fun onMotionEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } @Composable private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { val colors = LocalAndroidColorScheme.current Loading Loading @@ -820,107 +773,6 @@ private fun ToolbarButton( } } @Composable private fun AnimatedVisibilityScope.ButtonToEditWidgets( onClick: () -> Unit, onHide: () -> Unit, ) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide, ) { val colors = LocalAndroidColorScheme.current Button( modifier = Modifier.height(56.dp) .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) } .animateEnterExit( enter = fadeIn( initialAlpha = 0f, animationSpec = tween(durationMillis = 83, easing = LinearEasing) ), exit = fadeOut( animationSpec = tween( durationMillis = 83, delayMillis = 167, easing = LinearEasing ) ) ) .background(colors.secondary, RoundedCornerShape(50.dp)), onClick = onClick, ) { Row( modifier = Modifier.animateEnterExit( enter = fadeIn( animationSpec = tween( durationMillis = 167, delayMillis = 83, easing = LinearEasing ) ), exit = fadeOut( animationSpec = tween(durationMillis = 167, easing = LinearEasing) ) ) ) { Icon( imageVector = Icons.Outlined.Widgets, contentDescription = stringResource(R.string.button_to_configure_widgets_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.button_to_configure_widgets_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary ) } } } } @Composable private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHidePopup ) { val colors = LocalAndroidColorScheme.current Row( modifier = Modifier.height(56.dp) .background(colors.secondary, RoundedCornerShape(50.dp)) .padding(16.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Outlined.TouchApp, contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.popup_on_dismiss_cta_tile_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary, ) } } } @Composable private fun filledButtonColors(): ButtonColors { val colors = LocalAndroidColorScheme.current Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.communal.ui.compose import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @Composable fun CommunalTouchableSurface( viewModel: CommunalViewModel, modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = modifier .combinedClickable( onLongClick = viewModel::onLongClick, onClick = viewModel::onClick, interactionSource = interactionSource, indication = null, ) .onPreviewKeyEvent { viewModel.signalUserInteraction() false } .motionEventSpy { viewModel.signalUserInteraction() } ) { content() } } packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt 0 → 100644 +192 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.communal.ui.compose.section import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.PopupType import com.android.systemui.res.R import javax.inject.Inject class CommunalPopupSection @Inject constructor( private val viewModel: CommunalViewModel, ) { @Composable fun Popup() { val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) if (currentPopup == PopupType.CtaTile) { PopupOnDismissCtaTile(viewModel::onHidePopup) } AnimatedVisibility( visible = currentPopup == PopupType.CustomizeWidgetButton, modifier = Modifier.fillMaxSize() ) { ButtonToEditWidgets( onClick = { viewModel.onHidePopup() viewModel.onOpenWidgetEditor() }, onDismissRequest = { viewModel.onHidePopup() viewModel.setSelectedKey(null) } ) } } @Composable private fun AnimatedVisibilityScope.ButtonToEditWidgets( onClick: () -> Unit, onDismissRequest: () -> Unit, ) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onDismissRequest, ) { val colors = LocalAndroidColorScheme.current Button( modifier = Modifier.height(56.dp) .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) } .animateEnterExit( enter = fadeIn( initialAlpha = 0f, animationSpec = tween(durationMillis = 83, easing = LinearEasing) ), exit = fadeOut( animationSpec = tween( durationMillis = 83, delayMillis = 167, easing = LinearEasing ) ) ) .background(colors.secondary, RoundedCornerShape(50.dp)), onClick = onClick, ) { Row( modifier = Modifier.animateEnterExit( enter = fadeIn( animationSpec = tween( durationMillis = 167, delayMillis = 83, easing = LinearEasing ) ), exit = fadeOut( animationSpec = tween(durationMillis = 167, easing = LinearEasing) ) ) ) { Icon( imageVector = Icons.Outlined.Widgets, contentDescription = stringResource(R.string.button_to_configure_widgets_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.button_to_configure_widgets_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary ) } } } } @Composable private fun PopupOnDismissCtaTile(onDismissRequest: () -> Unit) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onDismissRequest ) { val colors = LocalAndroidColorScheme.current Row( modifier = Modifier.height(56.dp) .background(colors.secondary, RoundedCornerShape(50.dp)) .padding(16.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Outlined.TouchApp, contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.popup_on_dismiss_cta_tile_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary, ) } } } } packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +6 −3 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository Loading @@ -91,6 +92,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters Loading Loading @@ -154,13 +156,14 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { context.resources, kosmos.keyguardTransitionInteractor, kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), kosmos.communalSceneInteractor, kosmos.communalInteractor, kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), logcatLogBuffer("CommunalViewModelTest") ) } Loading Loading @@ -358,7 +361,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val currentPopup by collectLastValue(underTest.currentPopup) assertThat(currentPopup).isNull() underTest.onShowCustomizeWidgetButton() underTest.onLongClick() assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton) advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS) assertThat(currentPopup).isNull() Loading @@ -370,7 +373,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) val currentPopup by collectLastValue(underTest.currentPopup) underTest.onShowCustomizeWidgetButton() underTest.onLongClick() assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton) underTest.onHidePopup() Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +69 −63 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection Loading @@ -44,14 +45,17 @@ constructor( private val lockSection: LockSection, private val bottomAreaSection: BottomAreaSection, private val ambientStatusBarSection: AmbientStatusBarSection, private val communalPopupSection: CommunalPopupSection, ) { @Composable fun SceneScope.Content(modifier: Modifier = Modifier) { CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) { Layout( modifier = modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(), content = { Box(modifier = Modifier.fillMaxSize()) { with(communalPopupSection) { Popup() } with(ambientStatusBarSection) { AmbientStatusBar(modifier = Modifier.fillMaxWidth()) } Loading Loading @@ -97,7 +101,8 @@ constructor( val bottomAreaPlaceable = bottomAreaMeasurable.measure( noMinConstraints.copy( maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) maxHeight = (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0) ) ) Loading @@ -123,3 +128,4 @@ constructor( } } } }
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +19 −167 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.widget.FrameLayout import android.widget.RemoteViews import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState Loading Loading @@ -71,7 +70,6 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets import androidx.compose.material3.Button import androidx.compose.material3.ButtonColors Loading Loading @@ -103,10 +101,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow Loading @@ -126,13 +121,11 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.animation.Easings.Emphasized Loading @@ -151,7 +144,6 @@ import com.android.systemui.communal.ui.compose.extensions.observeTaps import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.PopupType import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R Loading @@ -171,7 +163,6 @@ fun CommunalHub( ) { val communalContent by viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } Loading Loading @@ -225,31 +216,17 @@ fun CommunalHub( gridCoordinates?.let { offset - it.positionInWindow() - contentOffset } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } // Display the button only when the gesture initiates from widgets, // the CTA tile, or an empty area on the screen. UMO/smartspace have // their own long-press handlers. To prevent user confusion, we // should // not display this button. if ( index == null || communalContent[index].isWidgetContent() || communalContent[index] is CommunalContentModel.CtaTileInViewMode ) { viewModel.onShowCustomizeWidgetButton() } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) } val key = index?.let { keyAtIndexIfEditable(communalContent, index) } // Handle long-click on widgets and set the selected index // correctly. We only handle widgets here because long click on // empty spaces is handled by CommunalPopupSection. if (key != null) { viewModel.onLongClick() viewModel.setSelectedKey(key) } } .onPreviewKeyEvent { onKeyEvent(viewModel) false } .motionEventSpy { onMotionEvent(viewModel) } }, ) { AccessibilityContainer(viewModel) { Loading Loading @@ -342,22 +319,6 @@ fun CommunalHub( ) } } if (currentPopup == PopupType.CtaTile) { PopupOnDismissCtaTile(viewModel::onHidePopup) } AnimatedVisibility( visible = currentPopup == PopupType.CustomizeWidgetButton, modifier = Modifier.fillMaxSize() ) { ButtonToEditWidgets( onClick = { viewModel.onHidePopup() viewModel.onOpenWidgetEditor(selectedKey.value) }, onHide = { viewModel.onHidePopup() } ) } if (viewModel is CommunalViewModel && dialogFactory != null) { val isEnableWidgetDialogShowing by Loading Loading @@ -413,14 +374,6 @@ fun CommunalHub( } } private fun onKeyEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } private fun onMotionEvent(viewModel: BaseCommunalViewModel) { viewModel.signalUserInteraction() } @Composable private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) { val colors = LocalAndroidColorScheme.current Loading Loading @@ -820,107 +773,6 @@ private fun ToolbarButton( } } @Composable private fun AnimatedVisibilityScope.ButtonToEditWidgets( onClick: () -> Unit, onHide: () -> Unit, ) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide, ) { val colors = LocalAndroidColorScheme.current Button( modifier = Modifier.height(56.dp) .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) } .animateEnterExit( enter = fadeIn( initialAlpha = 0f, animationSpec = tween(durationMillis = 83, easing = LinearEasing) ), exit = fadeOut( animationSpec = tween( durationMillis = 83, delayMillis = 167, easing = LinearEasing ) ) ) .background(colors.secondary, RoundedCornerShape(50.dp)), onClick = onClick, ) { Row( modifier = Modifier.animateEnterExit( enter = fadeIn( animationSpec = tween( durationMillis = 167, delayMillis = 83, easing = LinearEasing ) ), exit = fadeOut( animationSpec = tween(durationMillis = 167, easing = LinearEasing) ) ) ) { Icon( imageVector = Icons.Outlined.Widgets, contentDescription = stringResource(R.string.button_to_configure_widgets_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.button_to_configure_widgets_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary ) } } } } @Composable private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHidePopup ) { val colors = LocalAndroidColorScheme.current Row( modifier = Modifier.height(56.dp) .background(colors.secondary, RoundedCornerShape(50.dp)) .padding(16.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Outlined.TouchApp, contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.popup_on_dismiss_cta_tile_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary, ) } } } @Composable private fun filledButtonColors(): ButtonColors { val colors = LocalAndroidColorScheme.current Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.communal.ui.compose import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.pointer.motionEventSpy import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class) @Composable fun CommunalTouchableSurface( viewModel: CommunalViewModel, modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit, ) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = modifier .combinedClickable( onLongClick = viewModel::onLongClick, onClick = viewModel::onClick, interactionSource = interactionSource, indication = null, ) .onPreviewKeyEvent { viewModel.signalUserInteraction() false } .motionEventSpy { viewModel.signalUserInteraction() } ) { content() } }
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt 0 → 100644 +192 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.communal.ui.compose.section import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.PopupType import com.android.systemui.res.R import javax.inject.Inject class CommunalPopupSection @Inject constructor( private val viewModel: CommunalViewModel, ) { @Composable fun Popup() { val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) if (currentPopup == PopupType.CtaTile) { PopupOnDismissCtaTile(viewModel::onHidePopup) } AnimatedVisibility( visible = currentPopup == PopupType.CustomizeWidgetButton, modifier = Modifier.fillMaxSize() ) { ButtonToEditWidgets( onClick = { viewModel.onHidePopup() viewModel.onOpenWidgetEditor() }, onDismissRequest = { viewModel.onHidePopup() viewModel.setSelectedKey(null) } ) } } @Composable private fun AnimatedVisibilityScope.ButtonToEditWidgets( onClick: () -> Unit, onDismissRequest: () -> Unit, ) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onDismissRequest, ) { val colors = LocalAndroidColorScheme.current Button( modifier = Modifier.height(56.dp) .graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) } .animateEnterExit( enter = fadeIn( initialAlpha = 0f, animationSpec = tween(durationMillis = 83, easing = LinearEasing) ), exit = fadeOut( animationSpec = tween( durationMillis = 83, delayMillis = 167, easing = LinearEasing ) ) ) .background(colors.secondary, RoundedCornerShape(50.dp)), onClick = onClick, ) { Row( modifier = Modifier.animateEnterExit( enter = fadeIn( animationSpec = tween( durationMillis = 167, delayMillis = 83, easing = LinearEasing ) ), exit = fadeOut( animationSpec = tween(durationMillis = 167, easing = LinearEasing) ) ) ) { Icon( imageVector = Icons.Outlined.Widgets, contentDescription = stringResource(R.string.button_to_configure_widgets_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.button_to_configure_widgets_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary ) } } } } @Composable private fun PopupOnDismissCtaTile(onDismissRequest: () -> Unit) { Popup( alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onDismissRequest ) { val colors = LocalAndroidColorScheme.current Row( modifier = Modifier.height(56.dp) .background(colors.secondary, RoundedCornerShape(50.dp)) .padding(16.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = Icons.Outlined.TouchApp, contentDescription = stringResource(R.string.popup_on_dismiss_cta_tile_text), tint = colors.onSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.size(8.dp)) Text( text = stringResource(R.string.popup_on_dismiss_cta_tile_text), style = MaterialTheme.typography.titleSmall, color = colors.onSecondary, ) } } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +6 −3 Original line number Diff line number Diff line Loading @@ -75,6 +75,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository Loading @@ -91,6 +92,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters Loading Loading @@ -154,13 +156,14 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { context.resources, kosmos.keyguardTransitionInteractor, kosmos.keyguardInteractor, mock<KeyguardIndicationController>(), kosmos.communalSceneInteractor, kosmos.communalInteractor, kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), logcatLogBuffer("CommunalViewModelTest") ) } Loading Loading @@ -358,7 +361,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val currentPopup by collectLastValue(underTest.currentPopup) assertThat(currentPopup).isNull() underTest.onShowCustomizeWidgetButton() underTest.onLongClick() assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton) advanceTimeBy(POPUP_AUTO_HIDE_TIMEOUT_MS) assertThat(currentPopup).isNull() Loading @@ -370,7 +373,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) val currentPopup by collectLastValue(underTest.currentPopup) underTest.onShowCustomizeWidgetButton() underTest.onLongClick() assertThat(currentPopup).isEqualTo(PopupType.CustomizeWidgetButton) underTest.onHidePopup() Loading