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

Commit 00902b1d authored by Andreas Miko's avatar Andreas Miko Committed by Android (Google) Code Review
Browse files

Merge changes from topics "bundle-icons", "bundle-pill", "bundle-switch" into main

* changes:
  Make ENR getViewAtPosition calculation recursive
  Polish BundleHeader guts icons
  Fix expansion pill background not showing / overlapping
  Add Bundle guts done/apply and switch logic
parents 94368cdd f11ff3cc
Loading
Loading
Loading
Loading
+11 −20
Original line number Diff line number Diff line
@@ -28,6 +28,9 @@ import androidx.compose.foundation.layout.Spacer
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.filled.Check
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -35,15 +38,10 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -90,10 +88,9 @@ private fun TopRow(viewModel: BundleHeaderGutsViewModel, modifier: Modifier = Mo
        )

        Image(
            painter = painterResource(R.drawable.ic_settings_24dp),
            // TODO(b/409748420): Add correct CD
            imageVector = Icons.Default.Settings,
            contentDescription =
                stringResource(com.android.systemui.res.R.string.notification_more_settings),
                stringResource(com.android.systemui.res.R.string.accessibility_long_click_tile),
            modifier =
                Modifier.size(24.dp)
                    .clickable(
@@ -138,22 +135,17 @@ private fun ContentRow(viewModel: BundleHeaderGutsViewModel, modifier: Modifier
            )
        }

        var checked by remember { mutableStateOf(true) }

        Switch(
            checked = checked,
            // TODO(b/409748420): Implement proper checked logic
            onCheckedChange = { checked = !checked },
            checked = viewModel.switchState,
            onCheckedChange = { viewModel.switchState = !viewModel.switchState },
            thumbContent = {
                Icon(
                    // TODO(b/409748420): Add correct icon
                    painter = painterResource(R.drawable.ic_check_circle_24px),
                    imageVector = Icons.Default.Check,
                    contentDescription = null,
                    modifier = Modifier.size(SwitchDefaults.IconSize),
                    tint = MaterialTheme.colorScheme.onSecondaryContainer,
                )
            },
            // TODO(b/409748420): Implement correct switch colors
        )
    }
}
@@ -172,7 +164,7 @@ private fun BottomRow(viewModel: BundleHeaderGutsViewModel, modifier: Modifier =
                modifier
                    .padding(vertical = 13.dp)
                    .clickable(
                        onClick = viewModel.onDoneClicked,
                        onClick = viewModel.onDismissClicked,
                        indication = null,
                        interactionSource = null,
                    ),
@@ -180,16 +172,15 @@ private fun BottomRow(viewModel: BundleHeaderGutsViewModel, modifier: Modifier =

        Spacer(modifier = Modifier.weight(1f))

        // TODO(b/409748420): Implement done/apply switch
        Text(
            text = stringResource(R.string.done_label),
            text = stringResource(viewModel.getDoneOrApplyButtonText()),
            style = MaterialTheme.typography.titleSmallEmphasized,
            color = MaterialTheme.colorScheme.primary,
            modifier =
                modifier
                    .padding(vertical = 13.dp)
                    .clickable(
                        onClick = viewModel.onDismissClicked,
                        onClick = { viewModel.onDoneOrApplyClicked() },
                        indication = null,
                        interactionSource = null,
                    ),
+12 −10
Original line number Diff line number Diff line
@@ -54,7 +54,6 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementColorAsState
import com.android.compose.animation.scene.animateElementFloatAsState
import com.android.compose.ui.graphics.painter.rememberDrawablePainter

@@ -141,6 +140,8 @@ fun ContentScope.ExpansionControl(
@Composable
private fun ContentScope.PillBackground(modifier: Modifier = Modifier) {
    val surfaceColor = notificationElementSurfaceColor()
    // Needs to be a shared element so it does not overlap while animating
    ElementWithValues(NotificationRowPrimitives.Elements.PillBackground, modifier) {
        Box(
            modifier =
                Modifier.drawBehind {
@@ -151,6 +152,7 @@ private fun ContentScope.PillBackground(modifier: Modifier = Modifier) {
                }
        )
    }
}

@Composable
@ReadOnlyComposable
+96 −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.statusbar.notification.row.ui.viewmodel

import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.row.data.repository.TEST_BUNDLE_SPEC
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationBundleUi.FLAG_NAME)
class BundleHeaderGutsViewModelTest : SysuiTestCase() {

    @get:Rule val rule: MockitoRule = MockitoJUnit.rule()

    @Mock private lateinit var mockDisableBundle: () -> Unit

    @Mock private lateinit var mockCloseGuts: () -> Unit

    @Mock private lateinit var mockOnDismissClicked: () -> Unit

    @Mock private lateinit var mockOnSettingsClicked: () -> Unit

    private lateinit var underTest: BundleHeaderGutsViewModel

    @Before
    fun setUp() {
        MockitoAnnotations.openMocks(this)

        underTest =
            BundleHeaderGutsViewModel(
                titleText = TEST_BUNDLE_SPEC.titleText,
                summaryText = TEST_BUNDLE_SPEC.summaryText,
                bundleIcon = TEST_BUNDLE_SPEC.icon,
                disableBundle = mockDisableBundle,
                closeGuts = mockCloseGuts,
                onDismissClicked = mockOnDismissClicked,
                onSettingsClicked = mockOnSettingsClicked,
            )
    }

    @Test
    fun switchState_false_onDoneOrApplyClicked() {
        // Arrange
        underTest.switchState = false

        // Act
        underTest.onDoneOrApplyClicked()

        // Assert
        verify(mockDisableBundle).invoke()
        verify(mockOnDismissClicked).invoke()
        verify(mockCloseGuts, never()).invoke()
    }

    @Test
    fun switchState_true_onDoneOrApplyClicked() {
        // Arrange
        underTest.switchState = true

        // Act
        underTest.onDoneOrApplyClicked()

        // Assert
        verify(mockCloseGuts).invoke()
        verify(mockDisableBundle, never()).invoke()
        verify(mockOnDismissClicked, never()).invoke()
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -31,7 +31,13 @@ class BundleEntry(spec: BundleSpec) : PipelineEntry(spec.key) {
    override val bucket: Int = spec.bucket

    /** The model used by UI. */
    val bundleRepository = BundleRepository(spec.titleText, spec.icon, spec.summaryText)
    val bundleRepository =
        BundleRepository(
            titleText = spec.titleText,
            bundleIcon = spec.icon,
            summaryText = spec.summaryText,
            bundleType = spec.bundleType,
        )

    // TODO(b/394483200): move NotificationEntry's implementation to PipelineEntry?
    val isSensitive: MutableStateFlow<Boolean> = MutableStateFlow(false)
+11 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection

import android.app.NotificationChannel
import android.service.notification.Adjustment
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import com.android.internal.R
@@ -32,6 +33,12 @@ data class BundleSpec(
    @StringRes val summaryText: Int,
    @DrawableRes val icon: Int,
    @PriorityBucket val bucket: Int,

    /**
     * This is the id / [type] that identifies the bundle when calling APIs of
     * [android.app.INotificationManager]
     */
    @Adjustment.Types val bundleType: Int,
) {
    companion object {
        val PROMOTIONS =
@@ -42,6 +49,7 @@ data class BundleSpec(
                    com.android.systemui.res.R.string.notification_guts_promotions_summary,
                icon = com.android.settingslib.R.drawable.ic_promotions,
                bucket = BUCKET_PROMO,
                bundleType = Adjustment.TYPE_PROMOTION,
            )
        val SOCIAL_MEDIA =
            BundleSpec(
@@ -50,6 +58,7 @@ data class BundleSpec(
                summaryText = com.android.systemui.res.R.string.notification_guts_social_summary,
                icon = com.android.settingslib.R.drawable.ic_social,
                bucket = BUCKET_SOCIAL,
                bundleType = Adjustment.TYPE_SOCIAL_MEDIA,
            )
        val NEWS =
            BundleSpec(
@@ -58,6 +67,7 @@ data class BundleSpec(
                summaryText = com.android.systemui.res.R.string.notification_guts_news_summary,
                icon = com.android.settingslib.R.drawable.ic_news,
                bucket = BUCKET_NEWS,
                bundleType = Adjustment.TYPE_NEWS,
            )
        val RECOMMENDED =
            BundleSpec(
@@ -66,6 +76,7 @@ data class BundleSpec(
                summaryText = com.android.systemui.res.R.string.notification_guts_recs_summary,
                icon = com.android.settingslib.R.drawable.ic_recs,
                bucket = BUCKET_RECS,
                bundleType = Adjustment.TYPE_CONTENT_RECOMMENDATION,
            )
    }
}
Loading