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

Commit cd19bc7a authored by amehfooz's avatar amehfooz
Browse files

Add OngoingActivityChips composables to the status bar

This CL adds composable OngoingActivityChips to the status bar.
A composable view is added before the notification_icon_area
to match the existing ordering in the status bar.

Test: Make sure chips UI matches existing Android View chips.
Screenshot provided in b/372657935 comment#15
Bug: b/372657935
Flag: com.android.systemui.status_bar_chips_modernization

Change-Id: I611640cfd775c0e839e4b484e0bd0317b1f0a72f
parent 9462174a
Loading
Loading
Loading
Loading
+208 −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.statusbar.chips.ui.compose

import android.content.res.ColorStateList
import android.view.ViewGroup
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
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.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel

@Composable
fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
    val context = LocalContext.current
    val isClickable = model.onClickListener != null
    val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView

    // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
    // height of the chip is determined by the height of the background of the Row below.
    Box(
        contentAlignment = Alignment.Center,
        modifier =
            modifier
                .fillMaxHeight()
                .clickable(
                    enabled = isClickable,
                    onClick = {
                        // TODO(b/372657935): Implement click actions.
                    },
                ),
    ) {
        Row(
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically,
            modifier =
                Modifier.clip(
                        RoundedCornerShape(
                            dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius)
                        )
                    )
                    .height(dimensionResource(R.dimen.ongoing_appops_chip_height))
                    .widthIn(
                        min =
                            if (isClickable) {
                                dimensionResource(id = R.dimen.min_clickable_item_size)
                            } else {
                                0.dp
                            }
                    )
                    .background(Color(model.colors.background(context).defaultColor))
                    .padding(
                        horizontal =
                            if (hasEmbeddedIcon) {
                                0.dp
                            } else {
                                dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
                            }
                    ),
        ) {
            model.icon?.let { ChipIcon(viewModel = it, colors = model.colors) }

            val isIconOnly = model is OngoingActivityChipModel.Shown.IconOnly
            val isTextOnly = model.icon == null
            if (!isIconOnly) {
                ChipContent(
                    viewModel = model,
                    modifier =
                        Modifier.padding(
                            start =
                                if (isTextOnly || hasEmbeddedIcon) {
                                    0.dp
                                } else {
                                    dimensionResource(
                                        id = R.dimen.ongoing_activity_chip_icon_text_padding
                                    )
                                },
                            end =
                                if (hasEmbeddedIcon) {
                                    dimensionResource(
                                        id =
                                            R.dimen
                                                .ongoing_activity_chip_text_end_padding_for_embedded_padding_icon
                                    )
                                } else {
                                    0.dp
                                },
                        ),
                )
            }
        }
    }
}

@Composable
private fun ChipIcon(
    viewModel: OngoingActivityChipModel.ChipIcon,
    colors: ColorsModel,
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current

    when (viewModel) {
        is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
            val originalIcon = viewModel.impl
            val iconSizePx =
                context.resources.getDimensionPixelSize(
                    R.dimen.ongoing_activity_chip_embedded_padding_icon_size
                )
            AndroidView(
                modifier = modifier,
                factory = { _ ->
                    originalIcon.apply {
                        layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
                        imageTintList = ColorStateList.valueOf(colors.text(context))
                    }
                },
            )
        }

        is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> {
            Icon(
                icon = viewModel.impl,
                tint = Color(colors.text(context)),
                modifier =
                    modifier.size(dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size)),
            )
        }

        // TODO(b/372657935): Add recommended architecture implementation for
        // StatusBarNotificationIcons
        is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {}
    }
}

@Composable
private fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
    val context = LocalContext.current
    when (viewModel) {
        is OngoingActivityChipModel.Shown.Timer -> {
            ChronometerText(
                startTimeMillis = viewModel.startTimeMs,
                style = MaterialTheme.typography.labelLarge,
                color = Color(viewModel.colors.text(context)),
                modifier = modifier,
            )
        }

        is OngoingActivityChipModel.Shown.Countdown -> {
            ChipText(
                text = viewModel.secondsUntilStarted.toString(),
                color = Color(viewModel.colors.text(context)),
                style = MaterialTheme.typography.labelLarge,
                modifier = modifier.neverDecreaseWidth(),
                backgroundColor = Color(viewModel.colors.background(context).defaultColor),
            )
        }

        is OngoingActivityChipModel.Shown.Text -> {
            ChipText(
                text = viewModel.text,
                color = Color(viewModel.colors.text(context)),
                style = MaterialTheme.typography.labelLarge,
                modifier = modifier,
                backgroundColor = Color(viewModel.colors.background(context).defaultColor),
            )
        }

        is OngoingActivityChipModel.Shown.ShortTimeDelta -> {
            // TODO(b/372657935): Implement ShortTimeDelta content in compose.
        }
    }
}
+47 −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.statusbar.chips.ui.compose

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel

@Composable
fun OngoingActivityChips(chips: MultipleOngoingActivityChipsModel, modifier: Modifier = Modifier) {
    Row(
        // TODO(b/372657935): Remove magic numbers for padding and spacing.
        modifier = modifier.fillMaxHeight().padding(horizontal = 6.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        // TODO(b/372657935): Make sure chips are only shown when there is enough horizontal
        // space.
        if (chips.primary is OngoingActivityChipModel.Shown) {
            OngoingActivityChip(model = chips.primary)
        }
        if (chips.secondary is OngoingActivityChipModel.Shown) {
            OngoingActivityChip(model = chips.secondary)
        }
    }
}
+42 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -33,9 +34,11 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.keyguard.AlphaOptimizedLinearLayout
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
@@ -158,6 +161,45 @@ fun StatusBarRoot(
                            darkIconDispatcher,
                        )
                    iconController.addIconGroup(darkIconManager)

                    if (StatusBarChipsModernization.isEnabled) {
                        val startSideExceptHeadsUp =
                            phoneStatusBarView.requireViewById<LinearLayout>(
                                R.id.status_bar_start_side_except_heads_up
                            )

                        val composeView =
                            ComposeView(context).apply {
                                layoutParams =
                                    LinearLayout.LayoutParams(
                                        LinearLayout.LayoutParams.WRAP_CONTENT,
                                        LinearLayout.LayoutParams.WRAP_CONTENT,
                                    )

                                setViewCompositionStrategy(
                                    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
                                )

                                setContent {
                                    PlatformTheme {
                                        val chips by
                                            statusBarViewModel.ongoingActivityChips
                                                .collectAsStateWithLifecycle()
                                        OngoingActivityChips(chips = chips)
                                    }
                                }
                            }

                        // Add the composable container for ongoingActivityChips before the
                        // notification_icon_area to maintain the same ordering for ongoing activity
                        // chips in the status bar layout.
                        val notificationIconAreaIndex =
                            startSideExceptHeadsUp.indexOfChild(
                                startSideExceptHeadsUp.findViewById(R.id.notification_icon_area)
                            )
                        startSideExceptHeadsUp.addView(composeView, notificationIconAreaIndex)
                    }

                    HomeStatusBarIconBlockListBinder.bind(
                        statusIconContainer,
                        darkIconManager,