Merge "Simplify edit tile list state" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
index 73a0039..45262ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -40,21 +40,19 @@
         TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
 
         // Start the drag movement
-        val movingTileSpec = TestEditTiles[0].tileSpec
-        underTest.onStarted(movingTileSpec)
+        underTest.onStarted(TestEditTiles[0])
 
         // Assert that the correct tile is marked as moving
         TestEditTiles.forEach {
-            assertThat(underTest.isMoving(it.tileSpec)).isEqualTo(movingTileSpec == it.tileSpec)
+            assertThat(underTest.isMoving(it.tileSpec))
+                .isEqualTo(TestEditTiles[0].tileSpec == it.tileSpec)
         }
     }
 
     @Test
     fun onMoved_updatesList() {
-        val movingTileSpec = TestEditTiles[0].tileSpec
-
         // Start the drag movement
-        underTest.onStarted(movingTileSpec)
+        underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
         underTest.onMoved(listState.tiles[5].tileSpec)
@@ -67,10 +65,8 @@
 
     @Test
     fun onDrop_resetsMovingTile() {
-        val movingTileSpec = TestEditTiles[0].tileSpec
-
         // Start the drag movement
-        underTest.onStarted(movingTileSpec)
+        underTest.onStarted(TestEditTiles[0])
 
         // Move the tile to the end of the list
         underTest.onMoved(listState.tiles[5].tileSpec)
@@ -84,16 +80,15 @@
 
     @Test
     fun onMoveOutOfBounds_removeMovingTileFromCurrentList() {
-        val movingTileSpec = TestEditTiles[0].tileSpec
-
         // Start the drag movement
-        underTest.onStarted(movingTileSpec)
+        underTest.onStarted(TestEditTiles[0])
 
         // Move the tile outside of the list
         underTest.movedOutOfBounds()
 
         // Asserts the moving tile is not current
-        assertThat(listState.tiles.first { it.tileSpec == movingTileSpec }.isCurrent).isFalse()
+        assertThat(listState.tiles.firstOrNull { it.tileSpec == TestEditTiles[0].tileSpec })
+            .isNull()
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 517b601..e76d389 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -33,22 +33,25 @@
     val underTest = EditTileListState(TestEditTiles)
 
     @Test
-    fun movingNonExistentTile_listUnchanged() {
-        underTest.move(TileSpec.create("other_tile"), TestEditTiles[0].tileSpec)
+    fun movingNonExistentTile_tileAdded() {
+        val newTile = createEditTile("other_tile", false)
+        underTest.move(newTile, TestEditTiles[0].tileSpec)
 
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+        assertThat(underTest.tiles[0]).isEqualTo(newTile)
+        assertThat(underTest.tiles.subList(1, underTest.tiles.size))
+            .containsExactly(*TestEditTiles.toTypedArray())
     }
 
     @Test
     fun movingTileToNonExistentTarget_listUnchanged() {
-        underTest.move(TestEditTiles[0].tileSpec, TileSpec.create("other_tile"))
+        underTest.move(TestEditTiles[0], TileSpec.create("other_tile"))
 
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
     }
 
     @Test
     fun movingTileToItself_listUnchanged() {
-        underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[0].tileSpec)
+        underTest.move(TestEditTiles[0], TestEditTiles[0].tileSpec)
 
         assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
     }
@@ -56,7 +59,7 @@
     @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)
+        underTest.move(TestEditTiles[0], TestEditTiles[1].tileSpec)
 
         // Assert the tiles 0 and 1 have changed places.
         assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
@@ -67,22 +70,12 @@
             .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)
+    fun removingTile_listUpdates() {
+        // Remove tile at index 0
+        underTest.remove(TestEditTiles[0].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())
+        // Assert the tile was removed
+        assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 782fb2a..71deeb6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -14,12 +14,9 @@
  * 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
@@ -32,11 +29,12 @@
 import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.draganddrop.mimeTypes
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
 @Composable
 fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
-    val sourceSpec: MutableState<TileSpec?> = remember { mutableStateOf(null) }
+    val sourceSpec: MutableState<EditTileViewModel?> = remember { mutableStateOf(null) }
     return remember(listState) { DragAndDropState(sourceSpec, listState) }
 }
 
@@ -45,7 +43,7 @@
  * drop events.
  */
 class DragAndDropState(
-    val sourceSpec: MutableState<TileSpec?>,
+    val sourceSpec: MutableState<EditTileViewModel?>,
     private val listState: EditTileListState
 ) {
     val dragInProgress: Boolean
@@ -53,15 +51,15 @@
 
     /** 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
+        return sourceSpec.value?.let { listState.indexOf(it.tileSpec) } ?: -1
     }
 
     fun isMoving(tileSpec: TileSpec): Boolean {
-        return sourceSpec.value?.let { it == tileSpec } ?: false
+        return sourceSpec.value?.let { it.tileSpec == tileSpec } ?: false
     }
 
-    fun onStarted(spec: TileSpec) {
-        sourceSpec.value = spec
+    fun onStarted(tile: EditTileViewModel) {
+        sourceSpec.value = tile
     }
 
     fun onMoved(targetSpec: TileSpec) {
@@ -71,7 +69,7 @@
     fun movedOutOfBounds() {
         // Removing the tiles from the current tile grid if it moves out of bounds. This clears
         // the spacer and makes it apparent that dropping the tile at that point would remove it.
-        sourceSpec.value?.let { listState.removeFromCurrent(it) }
+        sourceSpec.value?.let { listState.remove(it.tileSpec) }
     }
 
     fun onDrop() {
@@ -100,7 +98,7 @@
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
                     return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it, dragAndDropState.currentPosition())
+                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -114,7 +112,7 @@
     return dragAndDropTarget(
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
+                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
         },
         target = target,
     )
@@ -137,7 +135,7 @@
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
                     return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it)
+                        onDrop(it.tileSpec)
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -179,7 +177,7 @@
 
                 override fun onDrop(event: DragAndDropEvent): Boolean {
                     return dragAndDropState.sourceSpec.value?.let {
-                        onDrop(it, dragAndDropState.currentPosition())
+                        onDrop(it.tileSpec, dragAndDropState.currentPosition())
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -190,23 +188,23 @@
         target = target,
         shouldStartDragAndDrop = { event ->
             event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
+                dragAndDropState.sourceSpec.value?.let { acceptDrops(it.tileSpec) } ?: false
         },
     )
 }
 
 fun Modifier.dragAndDropTileSource(
-    tileSpec: TileSpec,
+    tile: EditTileViewModel,
     onTap: (TileSpec) -> Unit,
     onDoubleTap: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState
 ): Modifier {
     return dragAndDropSource {
         detectTapGestures(
-            onTap = { onTap(tileSpec) },
-            onDoubleTap = { onDoubleTap(tileSpec) },
+            onTap = { onTap(tile.tileSpec) },
+            onDoubleTap = { onDoubleTap(tile.tileSpec) },
             onLongPress = {
-                dragAndDropState.onStarted(tileSpec)
+                dragAndDropState.onStarted(tile)
 
                 // 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
@@ -216,7 +214,7 @@
                         ClipData(
                             QsDragAndDrop.CLIPDATA_LABEL,
                             arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
-                            ClipData.Item(tileSpec.spec)
+                            ClipData.Item(tile.tileSpec.spec)
                         )
                     )
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 34876c4..e0fed28 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -34,28 +34,25 @@
 class EditTileListState(tiles: List<EditTileViewModel>) {
     val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()
 
-    fun move(tileSpec: TileSpec, target: TileSpec) {
-        val fromIndex = indexOf(tileSpec)
+    fun move(tile: EditTileViewModel, target: TileSpec) {
+        val fromIndex = indexOf(tile.tileSpec)
         val toIndex = indexOf(target)
 
-        if (fromIndex == -1 || toIndex == -1 || fromIndex == toIndex) {
+        if (toIndex == -1 || fromIndex == toIndex) {
             return
         }
 
-        val isMovingToCurrent = tiles[toIndex].isCurrent
-        tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) }
+        if (fromIndex == -1) {
+            // If tile isn't in the list, simply insert it
+            tiles.add(toIndex, tile)
+        } else {
+            // If tile is present in the list, move it
+            tiles.apply { add(toIndex, removeAt(fromIndex)) }
+        }
     }
 
-    /**
-     * Sets the [TileSpec] as a non-current tile. Use this when a tile is dragged out of the current
-     * tile grid.
-     */
-    fun removeFromCurrent(tileSpec: TileSpec) {
-        val fromIndex = indexOf(tileSpec)
-        if (fromIndex >= 0 && fromIndex < tiles.size) {
-            // Mark the moving tile as non-current
-            tiles[fromIndex] = tiles[fromIndex].copy(isCurrent = false)
-        }
+    fun remove(tileSpec: TileSpec) {
+        tiles.removeIf { it.tileSpec == tileSpec }
     }
 
     fun indexOf(tileSpec: TileSpec): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index bd7956d..cb9d0f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -294,9 +294,9 @@
     onRemoveTile: (TileSpec) -> Unit,
     onResize: (TileSpec, Boolean) -> Unit,
 ) {
-    val currentListState = rememberEditListState(tiles)
+    val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+    val currentListState = rememberEditListState(currentTiles)
     val dragAndDropState = rememberDragAndDropState(currentListState)
-    val (currentTiles, otherTiles) = currentListState.tiles.partition { it.isCurrent }
 
     val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
         onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
@@ -329,7 +329,7 @@
             }
 
             CurrentTilesGrid(
-                currentTiles,
+                currentListState.tiles,
                 columns,
                 tilePadding,
                 isIconOnly,
@@ -480,16 +480,13 @@
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
 ) {
-    val (otherTilesStock, otherTilesCustom) =
-        tiles.filter { !dragAndDropState.isMoving(it.tileSpec) }.partition { it.appName == null }
+    val (otherTilesStock, otherTilesCustom) = tiles.partition { it.appName == null }
     val availableTileHeight = tileHeight(true)
     val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding)
 
     // Available tiles
     TileLazyGrid(
-        modifier =
-            Modifier.height(availableGridHeight)
-                .dragAndDropTileList(dragAndDropState, { false }, { _, _ -> }),
+        modifier = Modifier.height(availableGridHeight),
         columns = GridCells.Fixed(columns)
     ) {
         editTiles(
@@ -594,7 +591,7 @@
                         }
                         .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop)
                         .dragAndDropTileSource(
-                            viewModel.tileSpec,
+                            viewModel,
                             onClick,
                             onDoubleTap,
                             dragAndDropState,