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

Commit 0088e07b authored by Olivier St-Onge's avatar Olivier St-Onge Committed by Android (Google) Code Review
Browse files

Merge "Implement drag and drop for edit mode" into main

parents ba1cf687 685b186c
Loading
Loading
Loading
Loading
+107 −0
Original line number Original line 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.qs.panels.ui.compose

import androidx.compose.runtime.mutableStateOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class DragAndDropStateTest : SysuiTestCase() {
    private val listState = EditTileListState(TestEditTiles)
    private val underTest = DragAndDropState(mutableStateOf(null), listState)

    @Test
    fun isMoving_returnsCorrectValue() {
        // Asserts no tiles is moving
        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }

        // Start the drag movement
        val movingTileSpec = TestEditTiles[0].tileSpec
        underTest.onStarted(movingTileSpec)

        // Assert that the correct tile is marked as moving
        TestEditTiles.forEach {
            assertThat(underTest.isMoving(it.tileSpec)).isEqualTo(movingTileSpec == it.tileSpec)
        }
    }

    @Test
    fun onMoved_updatesList() {
        val movingTileSpec = TestEditTiles[0].tileSpec

        // Start the drag movement
        underTest.onStarted(movingTileSpec)

        // Move the tile to the end of the list
        underTest.onMoved(listState.tiles[5].tileSpec)
        assertThat(underTest.currentPosition()).isEqualTo(5)

        // Move the tile to the middle of the list
        underTest.onMoved(listState.tiles[2].tileSpec)
        assertThat(underTest.currentPosition()).isEqualTo(2)
    }

    @Test
    fun onDrop_resetsMovingTile() {
        val movingTileSpec = TestEditTiles[0].tileSpec

        // Start the drag movement
        underTest.onStarted(movingTileSpec)

        // Move the tile to the end of the list
        underTest.onMoved(listState.tiles[5].tileSpec)

        // Drop the tile
        underTest.onDrop()

        // Asserts no tiles is moving
        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
    }

    companion object {
        private fun createEditTile(tileSpec: String): EditTileViewModel {
            return EditTileViewModel(
                tileSpec = TileSpec.create(tileSpec),
                icon = Icon.Resource(0, null),
                label = Text.Loaded("unused"),
                appName = null,
                isCurrent = true,
                availableEditActions = emptySet(),
            )
        }

        private val TestEditTiles =
            listOf(
                createEditTile("tileA"),
                createEditTile("tileB"),
                createEditTile("tileC"),
                createEditTile("tileD"),
                createEditTile("tileE"),
                createEditTile("tileF"),
            )
    }
}
+110 −0
Original line number Original line 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.qs.panels.ui.compose

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class EditTileListStateTest : SysuiTestCase() {
    val underTest = EditTileListState(TestEditTiles)

    @Test
    fun movingNonExistentTile_listUnchanged() {
        underTest.move(TileSpec.create("other_tile"), TestEditTiles[0].tileSpec)

        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
    }

    @Test
    fun movingTileToNonExistentTarget_listUnchanged() {
        underTest.move(TestEditTiles[0].tileSpec, TileSpec.create("other_tile"))

        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
    }

    @Test
    fun movingTileToItself_listUnchanged() {
        underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[0].tileSpec)

        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
    }

    @Test
    fun movingTileToSameSection_listUpdates() {
        // Move tile at index 0 to index 1. Tile 0 should remain current.
        underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[1].tileSpec)

        // Assert the tiles 0 and 1 have changed places.
        assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
        assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[0])

        // Assert the rest of the list is unchanged
        assertThat(underTest.tiles.subList(2, 5))
            .containsExactly(*TestEditTiles.subList(2, 5).toTypedArray())
    }

    @Test
    fun movingTileToDifferentSection_listAndTileUpdates() {
        // Move tile at index 0 to index 3. Tile 0 should no longer be current.
        underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[3].tileSpec)

        // Assert tile 0 is now at index 3 and is no longer current.
        assertThat(underTest.tiles[3]).isEqualTo(TestEditTiles[0].copy(isCurrent = false))

        // Assert previous tiles have shifted places
        assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
        assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[2])
        assertThat(underTest.tiles[2]).isEqualTo(TestEditTiles[3])

        // Assert the rest of the list is unchanged
        assertThat(underTest.tiles.subList(4, 5))
            .containsExactly(*TestEditTiles.subList(4, 5).toTypedArray())
    }

    companion object {
        private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel {
            return EditTileViewModel(
                tileSpec = TileSpec.create(tileSpec),
                icon = Icon.Resource(0, null),
                label = Text.Loaded("unused"),
                appName = null,
                isCurrent = isCurrent,
                availableEditActions = emptySet(),
            )
        }

        private val TestEditTiles =
            listOf(
                createEditTile("tileA", true),
                createEditTile("tileB", true),
                createEditTile("tileC", true),
                createEditTile("tileD", false),
                createEditTile("tileE", false),
                createEditTile("tileF", false),
            )
    }
}
+180 −0
Original line number Original line 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.
 */

@file:OptIn(ExperimentalFoundationApi::class)

package com.android.systemui.qs.panels.ui.compose

import android.content.ClipData
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.draganddrop.dragAndDropSource
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draganddrop.DragAndDropEvent
import androidx.compose.ui.draganddrop.DragAndDropTarget
import androidx.compose.ui.draganddrop.DragAndDropTransferData
import androidx.compose.ui.draganddrop.mimeTypes
import com.android.systemui.qs.pipeline.shared.TileSpec

@Composable
fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
    val sourceSpec: MutableState<TileSpec?> = remember { mutableStateOf(null) }
    return remember(listState) { DragAndDropState(sourceSpec, listState) }
}

/**
 * Holds the [TileSpec] of the tile being moved and modify the [EditTileListState] based on drag and
 * drop events.
 */
class DragAndDropState(
    val sourceSpec: MutableState<TileSpec?>,
    private val listState: EditTileListState
) {
    /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
    fun currentPosition(): Int {
        return sourceSpec.value?.let { listState.indexOf(it) } ?: -1
    }

    fun isMoving(tileSpec: TileSpec): Boolean {
        return sourceSpec.value?.let { it == tileSpec } ?: false
    }

    fun onStarted(spec: TileSpec) {
        sourceSpec.value = spec
    }

    fun onMoved(targetSpec: TileSpec) {
        sourceSpec.value?.let { listState.move(it, targetSpec) }
    }

    fun onDrop() {
        sourceSpec.value = null
    }
}

/**
 * Registers a tile as a [DragAndDropTarget] to receive drag events and update the
 * [DragAndDropState] with the tile's position, which can be used to insert a temporary placeholder.
 *
 * @param dragAndDropState The [DragAndDropState] using the tiles list
 * @param tileSpec The [TileSpec] of the tile
 * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
 * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
 */
@Composable
fun Modifier.dragAndDropTile(
    dragAndDropState: DragAndDropState,
    tileSpec: TileSpec,
    acceptDrops: (TileSpec) -> Boolean,
    onDrop: (TileSpec, Int) -> Unit,
): Modifier {
    val target =
        remember(dragAndDropState) {
            object : DragAndDropTarget {
                override fun onDrop(event: DragAndDropEvent): Boolean {
                    return dragAndDropState.sourceSpec.value?.let {
                        onDrop(it, dragAndDropState.currentPosition())
                        dragAndDropState.onDrop()
                        true
                    } ?: false
                }

                override fun onEntered(event: DragAndDropEvent) {
                    dragAndDropState.onMoved(tileSpec)
                }
            }
        }
    return dragAndDropTarget(
        shouldStartDragAndDrop = { event ->
            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
                dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
        },
        target = target,
    )
}

/**
 * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
 * containers to catch drops outside of tiles.
 *
 * @param dragAndDropState The [DragAndDropState] using the tiles list
 * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
 * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
 */
@Composable
fun Modifier.dragAndDropTileList(
    dragAndDropState: DragAndDropState,
    acceptDrops: (TileSpec) -> Boolean,
    onDrop: (TileSpec, Int) -> Unit,
): Modifier {
    val target =
        remember(dragAndDropState) {
            object : DragAndDropTarget {
                override fun onDrop(event: DragAndDropEvent): Boolean {
                    return dragAndDropState.sourceSpec.value?.let {
                        onDrop(it, dragAndDropState.currentPosition())
                        dragAndDropState.onDrop()
                        true
                    } ?: false
                }
            }
        }
    return dragAndDropTarget(
        target = target,
        shouldStartDragAndDrop = { event ->
            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
                dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
        },
    )
}

fun Modifier.dragAndDropTileSource(
    tileSpec: TileSpec,
    onTap: (TileSpec) -> Unit,
    dragAndDropState: DragAndDropState
): Modifier {
    return dragAndDropSource {
        detectTapGestures(
            onTap = { onTap(tileSpec) },
            onLongPress = {
                dragAndDropState.onStarted(tileSpec)

                // The tilespec from the ClipData transferred isn't actually needed as we're moving
                // a tile within the same application. We're using a custom MIME type to limit the
                // drag event to QS.
                startTransfer(
                    DragAndDropTransferData(
                        ClipData(
                            QsDragAndDrop.CLIPDATA_LABEL,
                            arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
                            ClipData.Item(tileSpec.spec)
                        )
                    )
                )
            }
        )
    }
}

private object QsDragAndDrop {
    const val CLIPDATA_LABEL = "tilespec"
    const val TILESPEC_MIME_TYPE = "qstile/tilespec"
}
+52 −0
Original line number Original line 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.qs.panels.ui.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec

@Composable
fun rememberEditListState(
    tiles: List<EditTileViewModel>,
): EditTileListState {
    return remember(tiles) { EditTileListState(tiles) }
}

/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
class EditTileListState(tiles: List<EditTileViewModel>) {
    val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()

    fun move(tileSpec: TileSpec, target: TileSpec) {
        val fromIndex = indexOf(tileSpec)
        val toIndex = indexOf(target)

        if (fromIndex == -1 || toIndex == -1 || fromIndex == toIndex) {
            return
        }

        val isMovingToCurrent = tiles[toIndex].isCurrent
        tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) }
    }

    fun indexOf(tileSpec: TileSpec): Int {
        return tiles.indexOfFirst { it.tileSpec == tileSpec }
    }
}
+63 −21
Original line number Original line Diff line number Diff line
@@ -110,12 +110,15 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
        tiles: List<EditTileViewModel>,
        tiles: List<EditTileViewModel>,
        modifier: Modifier,
        modifier: Modifier,
        onAddTile: (TileSpec, Int) -> Unit,
        onAddTile: (TileSpec, Int) -> Unit,
        onRemoveTile: (TileSpec) -> Unit
        onRemoveTile: (TileSpec) -> Unit,
    ) {
    ) {
        val columns by viewModel.columns.collectAsStateWithLifecycle()
        val columns by viewModel.columns.collectAsStateWithLifecycle()
        val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
        val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()


        val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
        val listState = rememberEditListState(tiles)
        val dragAndDropState = rememberDragAndDropState(listState)

        val (currentTiles, otherTiles) = listState.tiles.partition { it.isCurrent }
        val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
        val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
            onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
            onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
        }
        }
@@ -156,20 +159,24 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
                largeTileHeight = largeTileHeight,
                largeTileHeight = largeTileHeight,
                iconTileHeight = iconTileHeight,
                iconTileHeight = iconTileHeight,
                tilePadding = tilePadding,
                tilePadding = tilePadding,
                onRemoveTile = onRemoveTile,
                onAdd = onAddTile,
                onRemove = onRemoveTile,
                isIconOnly = viewModel::isIconTile,
                isIconOnly = viewModel::isIconTile,
                columns = columns,
                columns = columns,
                showLabels = showLabels,
                showLabels = showLabels,
                dragAndDropState = dragAndDropState,
            )
            )
            AvailableTiles(
            AvailableTiles(
                tiles = otherTiles,
                tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) },
                largeTileHeight = largeTileHeight,
                largeTileHeight = largeTileHeight,
                iconTileHeight = iconTileHeight,
                iconTileHeight = iconTileHeight,
                tilePadding = tilePadding,
                tilePadding = tilePadding,
                addTileToEnd = addTileToEnd,
                addTileToEnd = addTileToEnd,
                onRemove = onRemoveTile,
                isIconOnly = viewModel::isIconTile,
                isIconOnly = viewModel::isIconTile,
                showLabels = showLabels,
                showLabels = showLabels,
                columns = columns,
                columns = columns,
                dragAndDropState = dragAndDropState,
            )
            )
        }
        }
    }
    }
@@ -194,10 +201,12 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
        largeTileHeight: Dp,
        largeTileHeight: Dp,
        iconTileHeight: Dp,
        iconTileHeight: Dp,
        tilePadding: Dp,
        tilePadding: Dp,
        onRemoveTile: (TileSpec) -> Unit,
        onAdd: (TileSpec, Int) -> Unit,
        onRemove: (TileSpec) -> Unit,
        isIconOnly: (TileSpec) -> Boolean,
        isIconOnly: (TileSpec) -> Boolean,
        showLabels: Boolean,
        showLabels: Boolean,
        columns: Int,
        columns: Int,
        dragAndDropState: DragAndDropState,
    ) {
    ) {
        val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
        val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }


@@ -207,29 +216,40 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
        CurrentTilesContainer {
        CurrentTilesContainer {
            TileLazyGrid(
            TileLazyGrid(
                columns = GridCells.Fixed(columns),
                columns = GridCells.Fixed(columns),
                modifier = Modifier.height(largeGridHeight),
                modifier =
                    Modifier.height(largeGridHeight)
                        .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd)
            ) {
            ) {
                editTiles(
                editTiles(
                    largeTiles,
                    tiles = largeTiles,
                    ClickAction.REMOVE,
                    clickAction = ClickAction.REMOVE,
                    onRemoveTile,
                    onClick = onRemove,
                    { false },
                    isIconOnly = { false },
                    indicatePosition = true
                    dragAndDropState = dragAndDropState,
                    acceptDrops = { !isIconOnly(it) },
                    onDrop = onAdd,
                    indicatePosition = true,
                )
                )
            }
            }
        }
        }

        CurrentTilesContainer {
        CurrentTilesContainer {
            TileLazyGrid(
            TileLazyGrid(
                columns = GridCells.Fixed(columns),
                columns = GridCells.Fixed(columns),
                modifier = Modifier.height(smallGridHeight),
                modifier =
                    Modifier.height(smallGridHeight)
                        .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd)
            ) {
            ) {
                editTiles(
                editTiles(
                    smallTiles,
                    tiles = smallTiles,
                    ClickAction.REMOVE,
                    clickAction = ClickAction.REMOVE,
                    onRemoveTile,
                    onClick = onRemove,
                    { true },
                    isIconOnly = { true },
                    showLabels = showLabels,
                    showLabels = showLabels,
                    indicatePosition = true
                    dragAndDropState = dragAndDropState,
                    acceptDrops = { isIconOnly(it) },
                    onDrop = onAdd,
                    indicatePosition = true,
                )
                )
            }
            }
        }
        }
@@ -242,9 +262,11 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
        iconTileHeight: Dp,
        iconTileHeight: Dp,
        tilePadding: Dp,
        tilePadding: Dp,
        addTileToEnd: (TileSpec) -> Unit,
        addTileToEnd: (TileSpec) -> Unit,
        onRemove: (TileSpec) -> Unit,
        isIconOnly: (TileSpec) -> Boolean,
        isIconOnly: (TileSpec) -> Boolean,
        showLabels: Boolean,
        showLabels: Boolean,
        columns: Int,
        columns: Int,
        dragAndDropState: DragAndDropState,
    ) {
    ) {
        val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
        val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
        val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
        val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
@@ -258,13 +280,27 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
        val gridHeight =
        val gridHeight =
            largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2)
            largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2)


        val onDrop: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
            onRemove(tileSpec)
        }

        AvailableTilesContainer {
        AvailableTilesContainer {
            TileLazyGrid(
            TileLazyGrid(
                columns = GridCells.Fixed(columns),
                columns = GridCells.Fixed(columns),
                modifier = Modifier.height(gridHeight),
                modifier =
                    Modifier.height(gridHeight)
                        .dragAndDropTileList(dragAndDropState, { true }, onDrop)
            ) {
            ) {
                // Large tiles
                // Large tiles
                editTiles(largeTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
                editTiles(
                    largeTiles,
                    ClickAction.ADD,
                    addTileToEnd,
                    isIconOnly,
                    dragAndDropState,
                    acceptDrops = { true },
                    onDrop = onDrop,
                )
                fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
                fillUpRow(nTiles = largeTiles.size, columns = columns / 2)


                // Small tiles
                // Small tiles
@@ -273,7 +309,10 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
                    ClickAction.ADD,
                    ClickAction.ADD,
                    addTileToEnd,
                    addTileToEnd,
                    isIconOnly,
                    isIconOnly,
                    showLabels = showLabels
                    showLabels = showLabels,
                    dragAndDropState = dragAndDropState,
                    acceptDrops = { true },
                    onDrop = onDrop,
                )
                )
                fillUpRow(nTiles = smallTiles.size, columns = columns)
                fillUpRow(nTiles = smallTiles.size, columns = columns)


@@ -283,7 +322,10 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition
                    ClickAction.ADD,
                    ClickAction.ADD,
                    addTileToEnd,
                    addTileToEnd,
                    isIconOnly,
                    isIconOnly,
                    showLabels = showLabels
                    showLabels = showLabels,
                    dragAndDropState = dragAndDropState,
                    acceptDrops = { true },
                    onDrop = onDrop,
                )
                )
            }
            }
        }
        }
Loading