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

Commit 482a7344 authored by Ioana Alexandru's avatar Ioana Alexandru Committed by Android (Google) Code Review
Browse files

Merge changes from topics "collapsed_toplinetext", "prioritizedrow_sameimportance" into main

* changes:
  [Compose Notifs] Make PrioritizedRow handle same importance
  [Compose Notifs] Use TopLineText in collapsed notifs
parents 17ea0758 79531d99
Loading
Loading
Loading
Loading
+99 −35
Original line number Diff line number Diff line
@@ -136,6 +136,10 @@ public fun PrioritizedRow(
        init {
            check(hideWidth <= reducedWidth) { "hideWidth must be smaller than reducedWidth" }
        }

        fun shrinkable(): Boolean = !isSeparator && (reducedWidth < currentWidth)

        fun hideable(): Boolean = canHide && isVisible && (hideWidth < currentWidth)
    }

    fun List<LayoutCandidate>.previousVisibleChild(index: Int): LayoutCandidate? {
@@ -199,33 +203,89 @@ public fun PrioritizedRow(

            // SHRINK: The content doesn't fit, start shrinking elements down to their reduced width
            // based on priority
            for (i in sortedContent.indices) {
                val shrinkCandidate = sortedContent[i]
            var i = 0
            while (i < sortedContent.size) {
                if (!sortedContent[i].shrinkable()) {
                    i++
                    continue
                }

                // Take all candidates with the same importance, and shrink them simultaneously
                val importance = sortedContent[i].importance
                var shrinkableCnt = 1
                var lastShrinkableIdx = i
                for (j in i + 1 until sortedContent.size) {
                    if (sortedContent[j].importance != importance) break
                    if (sortedContent[j].shrinkable()) {
                        lastShrinkableIdx = j
                        shrinkableCnt++
                    }
                }

                var remainingShrinkables = shrinkableCnt
                for (j in i..lastShrinkableIdx) {
                    val shrinkCandidate = sortedContent[j]
                    if (!shrinkCandidate.shrinkable()) continue

                    // Distribute the space needed across all remaining candidates
                    val wantedSpace =
                        (overflow / remainingShrinkables) +
                            minOf(1, overflow % remainingShrinkables)
                    remainingShrinkables--

                // TODO: b/431222735 - Shrink elements with the same importance simultaneously.
                val shrinkableSpace = shrinkCandidate.currentWidth - shrinkCandidate.reducedWidth
                if (shrinkableSpace <= 0) continue
                val shrinkAmount = minOf(overflow, shrinkableSpace)
                    val shrinkableSpace =
                        shrinkCandidate.currentWidth - shrinkCandidate.reducedWidth
                    val shrinkAmount = minOf(wantedSpace, shrinkableSpace, overflow)
                    shrinkCandidate.currentWidth -= shrinkAmount

                    overflow -= shrinkAmount
                }

                if (overflow <= 0) break
                i = lastShrinkableIdx + 1
            }

            // HIDE: Content still doesn't fit, so we need to shrink elements further, and maybe
            // even hide them.
            var somethingWasHidden = false
            if (overflow > 0) {
                for (i in sortedContent.indices) {
                    val hideCandidate = sortedContent[i]
                    if (!hideCandidate.canHide || !hideCandidate.isVisible) continue
                i = 0
                while (i < sortedContent.size) {
                    if (!sortedContent[i].hideable()) {
                        i++
                        continue
                    }

                    // Take all hideable candidates with the same importance, and try to shrink them
                    // proportionally before hiding them
                    val importance = sortedContent[i].importance
                    var hideableCnt = 1
                    var lastHideableIdx = i
                    for (j in i + 1 until sortedContent.size) {
                        if (sortedContent[j].importance != importance) break
                        if (sortedContent[j].hideable()) {
                            lastHideableIdx = j
                            hideableCnt++
                        }
                    }

                    var remainingHideables = hideableCnt
                    for (j in i..lastHideableIdx) {
                        val hideCandidate = sortedContent[j]
                        if (!hideCandidate.hideable()) continue

                        // Distribute the space needed across all remaining candidates
                        val wantedSpace =
                            (overflow / remainingHideables) +
                                minOf(1, overflow % remainingHideables)
                        remainingHideables--

                        // One last attempt to shrink this element further
                        val shrinkableSpace = hideCandidate.currentWidth - hideCandidate.hideWidth
                    if (shrinkableSpace >= overflow) {
                        hideCandidate.currentWidth -= overflow
                        overflow = 0
                        break
                        if (shrinkableSpace >= wantedSpace) {
                            hideCandidate.currentWidth -= wantedSpace
                            overflow -= wantedSpace
                            continue
                        }

                        // Shrinking wouldn't be enough, so let's hide it
@@ -250,6 +310,10 @@ public fun PrioritizedRow(
                        overflow -= spaceToReclaim
                        if (overflow <= 0) break
                    }

                    if (overflow <= 0) break
                    i = lastHideableIdx + 1
                }
            }

            // REGROW: If hiding items created extra space, give it back to visible shrunk items.
+7 −3
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ internal fun ExpandedText(content: String, maxLines: Int, modifier: Modifier = M
@Composable
internal fun TopLineText(
    modifier: Modifier = Modifier,
    title: String? = null,
    appNameText: String? = null,
    headerTextSecondary: String? = null,
    headerText: String? = null,
@@ -87,9 +88,12 @@ internal fun TopLineText(
            }
        }

        // TODO: b/431222735 - Add a title with a separate style.
        if (appNameText != null) {
        if (title != null) {
            isFirstElement = false
            Title(title, Modifier.shrinkable(importance = 3, minWidth = reducedWidth))
        }
        if (appNameText != null) {
            maybeAddSeparator()
            TopLineComponentText(
                text = appNameText,
                modifier = Modifier.shrinkable(importance = 1, minWidth = reducedWidth),
@@ -101,7 +105,7 @@ internal fun TopLineText(
                text = headerTextSecondary,
                modifier =
                    Modifier.hideable(
                        importance = 4,
                        importance = 3,
                        reducedWidth = reducedWidth,
                        hideWidth = hideWidth,
                    ),
+16 −2
Original line number Diff line number Diff line
@@ -42,7 +42,19 @@ public fun NotificationContent(viewModel: NotificationViewModel, modifier: Modif
    if (!viewModel.isExpanded) {
        NotificationRow(
            viewModel,
            firstLine = { Title(viewModel.title) },
            firstLine = {
                TopLineText(
                    modifier = Modifier.padding(vertical = 2.dp),
                    title = viewModel.title,
                    // When collapsed, app name is only shown when there is no title
                    appNameText = if (viewModel.title == null) viewModel.appName else null,
                    headerTextSecondary = viewModel.headerTextSecondary,
                    headerText = viewModel.headerText,
                    // TODO: b/431222735 - Implement time/chronometer logic.
                    timeText = "now",
                    verificationText = viewModel.verificationText,
                )
            },
            secondLine = { viewModel.text?.let { CollapsedText(it) } },
            modifier,
        )
@@ -60,7 +72,9 @@ public fun NotificationContent(viewModel: NotificationViewModel, modifier: Modif
                    verificationText = viewModel.verificationText,
                )
            },
            secondLine = { Title(viewModel.title) },
            // TODO: b/431222735 - Consider showing the expanded text here when there is no title.
            //  this would require a mechanism for getting the text to wrap around the large icon.
            secondLine = { Title(viewModel.title ?: "") },
            modifier,
        ) {
            viewModel.text?.let { ExpandedText(it, maxLines = viewModel.maxLinesWhenExpanded) }
+1 −1
Original line number Diff line number Diff line
@@ -33,7 +33,7 @@ public interface NotificationViewModel {

    // TODO: b/431222735 - Make this nullable once we implement the top line fields.
    /** The title of the notification, emphasized in the content. */
    public val title: String
    public val title: String?
    /** The content text of the notification, shown below the title. */
    public val text: String?

+48 −39
Original line number Diff line number Diff line
@@ -95,6 +95,36 @@ class PrioritizedRowTest : SysuiTestCase() {
        rule.onNodeWithTag("row").assertWidthIsEqualTo(420.dp)
    }

    @Test
    fun width320dp_sameImportance_allShrinkablesShrink() {
        rule.setContent {
            CompositionLocalProvider(LocalDensity provides density) {
                PlatformTheme {
                    PrioritizedRow(modifier = Modifier.width(320.dp).testTag("row")) {
                        // All children have the same importance (everything else stays the same)
                        TestContent(forceSameImportance = true)
                    }
                }
            }
        }

        rule.onNodeWithTag("icon0").assertIsDisplayedWithWidth(iconWidth)
        rule.onNodeWithTag("spacer0").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithTag("icon1").assertIsDisplayedWithWidth(iconWidth)
        rule.onNodeWithTag("spacer1").assertIsDisplayedWithWidth(separatorWidth)
        // The required space is distributed across all 3 shrinkables, but they can't go below
        // reducedWidth.
        rule.onNodeWithText("High Importance").assertIsDisplayedWithWidth(reducedWidth)
        rule.onNodeWithTag("dot0").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("Medium (Shrinkable)").assertIsDisplayedWithWidth(57.5.dp)
        rule.onNodeWithTag("dot1").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithTag("Low (Hideable)").assertIsDisplayedWithWidth(64.5.dp)
        rule.onNodeWithTag("spacer2").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("FIXED").assertIsDisplayedWithWidth(fixedWidth)

        rule.onNodeWithTag("row").assertWidthIsEqualTo(320.dp)
    }

    @Test
    fun width400dp_lowPriorityRowShrinks() {
        rule.setContent {
@@ -183,21 +213,21 @@ class PrioritizedRowTest : SysuiTestCase() {
    }

    @Test
    fun width290dp_firstIconShrinks() {
    fun width280dp_startIconsShrink() {
        rule.setContent {
            CompositionLocalProvider(LocalDensity provides density) {
                PlatformTheme {
                    PrioritizedRow(modifier = Modifier.width(290.dp).testTag("row")) {
                    PrioritizedRow(modifier = Modifier.width(280.dp).testTag("row")) {
                        TestContent()
                    }
                }
            }
        }

        // First icon starts shrinking before being hidden
        rule.onNodeWithTag("icon0").assertIsDisplayedWithWidth(16.dp)
        // Start icons have the same importance, so they both start shrinking
        rule.onNodeWithTag("icon0").assertIsDisplayedWithWidth(15.dp)
        rule.onNodeWithTag("spacer0").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithTag("icon1").assertIsDisplayedWithWidth(iconWidth)
        rule.onNodeWithTag("icon1").assertIsDisplayedWithWidth(15.dp)
        rule.onNodeWithTag("spacer1").assertIsDisplayedWithWidth(separatorWidth)
        // High priority text doesn't shrink beyond reducedWidth
        rule.onNodeWithText("High Importance").assertIsDisplayedWithWidth(reducedWidth)
@@ -209,36 +239,7 @@ class PrioritizedRowTest : SysuiTestCase() {
        rule.onNodeWithTag("spacer2").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("FIXED").assertIsDisplayedWithWidth(fixedWidth)

        rule.onNodeWithTag("row").assertWidthIsEqualTo(290.dp)
    }

    @Test
    fun width240dp_firstIconHides() {
        rule.setContent {
            CompositionLocalProvider(LocalDensity provides density) {
                PlatformTheme {
                    PrioritizedRow(modifier = Modifier.width(240.dp).testTag("row")) {
                        TestContent()
                    }
                }
            }
        }

        // First icon and corresponding spacer disappear
        rule.onNodeWithTag("icon0").assertIsNotDisplayed()
        rule.onNodeWithTag("spacer0").assertIsNotDisplayed()
        // Second icon starts shrinking
        rule.onNodeWithTag("icon1").assertIsDisplayedWithWidth(5.dp)
        rule.onNodeWithTag("spacer1").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("High Importance").assertIsDisplayedWithWidth(reducedWidth)
        rule.onNodeWithTag("dot0").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("Medium (Shrinkable)").assertIsDisplayedWithWidth(reducedWidth)
        rule.onNodeWithTag("dot1").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithTag("Low (Hideable)").assertIsDisplayedWithWidth(reducedWidth)
        rule.onNodeWithTag("spacer2").assertIsDisplayedWithWidth(separatorWidth)
        rule.onNodeWithText("FIXED").assertIsDisplayedWithWidth(fixedWidth)

        rule.onNodeWithTag("row").assertWidthIsEqualTo(240.dp)
        rule.onNodeWithTag("row").assertWidthIsEqualTo(280.dp)
    }

    @Test
@@ -329,7 +330,7 @@ class PrioritizedRowTest : SysuiTestCase() {

    // Note: This composable is forked in the Compose Gallery app for interactive, manual testing.
    @Composable
    private fun PrioritizedRowScope.TestContent() {
    private fun PrioritizedRowScope.TestContent(forceSameImportance: Boolean = false) {
        // Note: This font family & size  (together with the fixed density configuration) means that
        // each character in a text composable will have a width of 5dp.
        val fontFamily = FontFamily.Monospace
@@ -355,7 +356,11 @@ class PrioritizedRowTest : SysuiTestCase() {
        // This text will be the last to shrink
        Text(
            text = "High Importance",
            modifier = Modifier.shrinkable(importance = 3, minWidth = reducedWidth),
            modifier =
                Modifier.shrinkable(
                    importance = if (forceSameImportance) 0 else 3,
                    minWidth = reducedWidth,
                ),
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            fontFamily = fontFamily,
@@ -371,7 +376,11 @@ class PrioritizedRowTest : SysuiTestCase() {
        // This text will shrink to its minWidth
        Text(
            text = "Medium (Shrinkable)",
            modifier = Modifier.shrinkable(importance = 2, minWidth = reducedWidth),
            modifier =
                Modifier.shrinkable(
                    importance = if (forceSameImportance) 0 else 2,
                    minWidth = reducedWidth,
                ),
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            fontFamily = fontFamily,
@@ -388,7 +397,7 @@ class PrioritizedRowTest : SysuiTestCase() {
        Row(
            modifier =
                Modifier.hideable(
                        importance = 1,
                        importance = if (forceSameImportance) 0 else 1,
                        reducedWidth = reducedWidth,
                        hideWidth = hideWidth,
                    )