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

Commit 901496fd authored by Lucas Silva's avatar Lucas Silva
Browse files

Implement resizing of ongoing content in responsive grid

In a responsive grid, we don't actually know how many rows/columns we
will have until we render the grid. Therefore in order to do dynamic
sizing of ongoing items, it needs to be done after the grid layout. We
therefore move this logic to a function which we can call from the ui
code.

Flag: com.android.systemui.communal_responsive_grid
Test: atest ResizeUtilsTest
Bug: 378171351
Change-Id: I6c1d345d6701b8b1cbb541902e1f1716d5c02717
parent 1224d358
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -187,6 +187,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.lifecycle.rememberViewModel
@@ -834,8 +835,16 @@ private fun BoxScope.CommunalHubLazyGrid(
        gridState = gridState,
        contentPadding = contentPadding,
    ) { sizeInfo ->
        /** Override spans based on the responsive grid size */
        val finalizedList =
            if (sizeInfo != null) {
                resizeOngoingItems(list, sizeInfo.gridSize.height)
            } else {
                list
            }

        itemsIndexed(
            items = list,
            items = finalizedList,
            key = { _, item -> item.key },
            contentType = { _, item -> item.key },
            span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) },
+222 −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.util

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
class ResizeUtilsTest : SysuiTestCase() {
    private val mockWidget =
        mock<CommunalContentModel.WidgetContent.Widget> {
            on { size } doReturn CommunalContentSize.Responsive(1)
        }

    @Test
    fun noOngoingContent() {
        val list = listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 2)

        assertThat(resized).containsExactly(mockWidget)
    }

    @Test
    fun singleOngoingContent_singleRowGrid() {
        val list = createOngoingListWithSize(1) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 1)

        assertThat(resized.map { it.size })
            .containsExactly(CommunalContentSize.Responsive(1), mockWidget.size)
            .inOrder()
    }

    @Test
    fun singleOngoingContent_twoRowGrid() {
        val list = createOngoingListWithSize(1) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 2)

        assertThat(resized.map { it.size })
            .containsExactly(CommunalContentSize.Responsive(2), mockWidget.size)
            .inOrder()
    }

    @Test
    fun singleOngoingContent_threeRowGrid() {
        val list = createOngoingListWithSize(1) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 3)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(2),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
        // A spacer should be added as the second element to avoid mixing widget content
        // with ongoing content.
        assertThat(resized[1]).isInstanceOf(CommunalContentModel.Spacer::class.java)
    }

    @Test
    fun twoOngoingContent_singleRowGrid() {
        val list = createOngoingListWithSize(2) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 1)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun twoOngoingContent_twoRowGrid() {
        val list = createOngoingListWithSize(2) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 2)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun twoOngoingContent_threeRowGrid() {
        val list = createOngoingListWithSize(2) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 3)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(2),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun threeOngoingContent_singleRowGrid() {
        val list = createOngoingListWithSize(3) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 1)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun threeOngoingContent_twoRowGrid() {
        val list = createOngoingListWithSize(3) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 2)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(2),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun threeOngoingContent_threeRowGrid() {
        val list = createOngoingListWithSize(3) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 3)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun fourOngoingContent_singleRowGrid() {
        val list = createOngoingListWithSize(4) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 1)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun fourOngoingContent_twoRowGrid() {
        val list = createOngoingListWithSize(4) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 2)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    @Test
    fun fourOngoingContent_threeRowGrid() {
        val list = createOngoingListWithSize(4) + listOf(mockWidget)
        val resized = resizeOngoingItems(list = list, numRows = 3)

        assertThat(resized.map { it.size })
            .containsExactly(
                CommunalContentSize.Responsive(2),
                CommunalContentSize.Responsive(1),
                CommunalContentSize.Responsive(2),
                CommunalContentSize.Responsive(1),
                mockWidget.size,
            )
            .inOrder()
    }

    private fun createOngoingListWithSize(size: Int): List<CommunalContentModel.Ongoing> {
        return List(size) { CommunalContentModel.Umo(createdTimestampMillis = 100) }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.Flags.communalResponsiveGrid
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
@@ -535,7 +536,9 @@ constructor(
                // Order by creation time descending.
                ongoingContent.sortByDescending { it.createdTimestampMillis }
                // Resize the items.
                if (!communalResponsiveGrid()) {
                    ongoingContent.resizeItems()
                }

                // Return the sorted and resized items.
                ongoingContent
+71 −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.util

import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize

object ResizeUtils {
    /**
     * Resizes ongoing items such that we don't mix regular content with ongoing content.
     *
     * NOTE: This is *NOT* a pure function, as it modifies items in the input list.
     *
     * Assumptions:
     * 1. Ongoing content is always at the start of the list.
     * 2. The maximum size of ongoing content is 2 rows.
     */
    fun resizeOngoingItems(
        list: List<CommunalContentModel>,
        numRows: Int,
    ): List<CommunalContentModel> {
        val finalizedList = mutableListOf<CommunalContentModel>()
        val numOngoing = list.count { it is CommunalContentModel.Ongoing }
        // Calculate the number of extra rows we have if each ongoing item were to take up a single
        // row. This is the number of rows we have to distribute across items.
        var extraRows =
            if (numOngoing % numRows == 0) {
                0
            } else {
                numRows - (numOngoing % numRows)
            }
        var remainingRows = numRows

        for (item in list) {
            if (item is CommunalContentModel.Ongoing) {
                if (remainingRows == 0) {
                    // Start a new column.
                    remainingRows = numRows
                }
                val newSize = if (extraRows > 0 && remainingRows > 1) 2 else 1
                item.size = CommunalContentSize.Responsive(newSize)
                finalizedList.add(item)
                extraRows -= (newSize - 1)
                remainingRows -= newSize
            } else {
                if (numOngoing > 0 && remainingRows > 0) {
                    finalizedList.add(
                        CommunalContentModel.Spacer(CommunalContentSize.Responsive(remainingRows))
                    )
                }
                remainingRows = -1
                finalizedList.add(item)
            }
        }
        return finalizedList
    }
}