diff options
7 files changed, 118 insertions, 18 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt index 19acb2e9839c..2f6a2b51d625 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt @@ -82,10 +82,6 @@ constructor( chipId = PopupChipId.MediaControl, icon = defaultIcon, chipText = model.songName.toString(), - isToggled = false, - // TODO(b/385202114): Show a popup containing the media carousal when the chip is - // toggled. - onToggle = {}, hoverBehavior = createHoverBehavior(model), ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt index 683b97166f3e..60615536ab67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt @@ -53,10 +53,11 @@ sealed class PopupChipModel { /** Default icon displayed on the chip */ val icon: Icon, val chipText: String, - val isToggled: Boolean = false, - val onToggle: () -> Unit, + val isPopupShown: Boolean = false, + val showPopup: () -> Unit = {}, + val hidePopup: () -> Unit = {}, val hoverBehavior: HoverBehavior = HoverBehavior.None, ) : PopupChipModel() { - override val logName = "Shown(id=$chipId, toggled=$isToggled)" + override val logName = "Shown(id=$chipId, toggled=$isPopupShown)" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt new file mode 100644 index 000000000000..8a66904ea59b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopup.kt @@ -0,0 +1,65 @@ +/* + * 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.statusbar.featurepods.popups.ui.compose + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import com.android.systemui.res.R +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId +import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel + +/** + * Displays a popup in the status bar area. The offset is calculated to draw the popup below the + * status bar. + */ +@Composable +fun StatusBarPopup(viewModel: PopupChipModel.Shown) { + val density = Density(LocalContext.current) + Popup( + properties = + PopupProperties( + focusable = false, + dismissOnBackPress = true, + dismissOnClickOutside = true, + ), + offset = + IntOffset( + x = 0, + y = with(density) { dimensionResource(R.dimen.status_bar_height).roundToPx() }, + ), + onDismissRequest = { viewModel.hidePopup() }, + ) { + Box(modifier = Modifier.padding(8.dp).wrapContentSize()) { + when (viewModel.chipId) { + is PopupChipId.MediaControl -> { + // TODO(b/385202114): Populate MediaControlPopup contents. + } + } + // Future popup types will be handled here. + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt index 34bef9d3ca3a..eb85d2f32f29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt @@ -52,14 +52,14 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM * the chip can show text containing contextual information. */ @Composable -fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifier) { - val hasHoverBehavior = model.hoverBehavior !is HoverBehavior.None +fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Modifier) { + val hasHoverBehavior = viewModel.hoverBehavior !is HoverBehavior.None val hoverInteractionSource = remember { MutableInteractionSource() } val isHovered by hoverInteractionSource.collectIsHoveredAsState() - val isToggled = model.isToggled + val isPopupShown = viewModel.isPopupShown val chipBackgroundColor = - if (isToggled) { + if (isPopupShown) { MaterialTheme.colorScheme.primaryContainer } else { MaterialTheme.colorScheme.surfaceContainerHighest @@ -72,7 +72,7 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie .padding(vertical = 4.dp) .animateContentSize() .thenIf(hasHoverBehavior) { Modifier.hoverable(hoverInteractionSource) } - .clickable { model.onToggle() }, + .thenIf(!isPopupShown) { Modifier.clickable { viewModel.showPopup() } }, color = chipBackgroundColor, ) { Row( @@ -82,14 +82,14 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie ) { val iconColor = if (isHovered) chipBackgroundColor else contentColorFor(chipBackgroundColor) - val hoverBehavior = model.hoverBehavior + val hoverBehavior = viewModel.hoverBehavior val iconBackgroundColor = contentColorFor(chipBackgroundColor) val iconInteractionSource = remember { MutableInteractionSource() } Icon( icon = when { isHovered && hoverBehavior is HoverBehavior.Button -> hoverBehavior.icon - else -> model.icon + else -> viewModel.icon }, modifier = Modifier.thenIf(isHovered) { @@ -109,7 +109,7 @@ fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifie ) Text( - text = model.chipText, + text = viewModel.chipText, style = MaterialTheme.typography.labelLarge, softWrap = false, overflow = TextOverflow.Ellipsis, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt index d35674d8dd9f..16538c93cf35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt @@ -31,10 +31,15 @@ fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Mo // TODO(b/385353140): Add padding and spacing for this container according to UX specs. Box { Row( - modifier = Modifier.padding(horizontal = 8.dp), + modifier = modifier.padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { - chips.forEach { chip -> StatusBarPopupChip(chip) } + chips.forEach { chip -> + StatusBarPopupChip(chip) + if (chip.isPopupShown) { + StatusBarPopup(chip) + } + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt index 786794fd3b37..33bf90defb48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModel.kt @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.featurepods.popups.ui.viewmodel import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.statusbar.featurepods.media.ui.viewmodel.MediaControlChipViewModel @@ -37,6 +39,9 @@ class StatusBarPopupChipsViewModel constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable() { private val hydrator: Hydrator = Hydrator("StatusBarPopupChipsViewModel.hydrator") + /** The ID of the current chip that is showing its popup, or `null` if no chip is shown. */ + private var currentShownPopupChipId by mutableStateOf<PopupChipId?>(null) + private val incomingPopupChipBundle: PopupChipBundle by hydrator.hydratedStateOf( traceName = "incomingPopupChipBundle", @@ -47,7 +52,14 @@ constructor(mediaControlChip: MediaControlChipViewModel) : ExclusiveActivatable( val shownPopupChips: List<PopupChipModel.Shown> by derivedStateOf { if (StatusBarPopupChips.isEnabled) { val bundle = incomingPopupChipBundle - listOfNotNull(bundle.media).filterIsInstance<PopupChipModel.Shown>() + + listOfNotNull(bundle.media).filterIsInstance<PopupChipModel.Shown>().map { chip -> + chip.copy( + isPopupShown = chip.chipId == currentShownPopupChipId, + showPopup = { currentShownPopupChipId = chip.chipId }, + hidePopup = { currentShownPopupChipId = null }, + ) + } } else { emptyList() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt index 32b6dc0e8f3d..510d6fe2b776 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/featurepods/popups/ui/viewmodel/StatusBarPopupChipsViewModelTest.kt @@ -71,4 +71,25 @@ class StatusBarPopupChipsViewModelTest : SysuiTestCase() { assertThat(shownPopupChips.first().chipId).isEqualTo(PopupChipId.MediaControl) } } + + @Test + fun shownPopupChips_mediaChipToggled_popupShown() = + kosmos.runTest { + val shownPopupChips = underTest.shownPopupChips + + val userMedia = MediaData(active = true, song = "test") + val instanceId = userMedia.instanceId + + mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + + Snapshot.takeSnapshot { + assertThat(shownPopupChips).hasSize(1) + val mediaChip = shownPopupChips.first() + assertThat(mediaChip.isPopupShown).isFalse() + + mediaChip.showPopup.invoke() + assertThat(shownPopupChips.first().isPopupShown).isTrue() + } + } } |