Merge "Fix Drag and drop bug when moving a tile within the current list" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
index 8b49d43..601779f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
@@ -54,8 +54,10 @@
 import com.android.systemui.settings.userTracker
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -103,9 +105,7 @@
             appName2,
         )
 
-    private val underTest: EditModeViewModel by lazy {
-        kosmos.editModeViewModel
-    }
+    private val underTest: EditModeViewModel by lazy { kosmos.editModeViewModel }
 
     @Before
     fun setUp() {
@@ -461,31 +461,87 @@
         }
 
     @Test
-    fun tileNotAvailable_notShowing() = with(kosmos) {
-        testScope.runTest {
-            val unavailableTile = "work"
-            qsTileFactory = FakeQSFactory { spec ->
-                FakeQSTile(userTracker.userId, spec != unavailableTile)
-            }
-            tileAvailabilityInteractorsMap = mapOf(
-                    unavailableTile to FakeTileAvailabilityInteractor(
-                            emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
+    fun tileNotAvailable_notShowing() =
+        with(kosmos) {
+            testScope.runTest {
+                val unavailableTile = "work"
+                qsTileFactory = FakeQSFactory { spec ->
+                    FakeQSTile(userTracker.userId, spec != unavailableTile)
+                }
+                tileAvailabilityInteractorsMap =
+                    mapOf(
+                        unavailableTile to
+                            FakeTileAvailabilityInteractor(
+                                emptyMap<Int, Flow<Boolean>>().withDefault { flowOf(false) }
+                            )
                     )
-            )
-            val tiles by collectLastValue(underTest.tiles)
-            val currentTiles =
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
                     mutableListOf(
-                            TileSpec.create("flashlight"),
-                            TileSpec.create("airplane"),
-                            TileSpec.create("alarm"),
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("alarm"),
                     )
-            currentTilesInteractor.setTiles(currentTiles)
+                currentTilesInteractor.setTiles(currentTiles)
 
-            underTest.startEditing()
+                underTest.startEditing()
 
-            assertThat(tiles!!.none { it.tileSpec == TileSpec.create(unavailableTile) }).isTrue()
+                assertThat(tiles!!.none { it.tileSpec == TileSpec.create(unavailableTile) })
+                    .isTrue()
+            }
         }
-    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun currentTiles_moveTileDown() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("internet"),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                underTest.startEditing()
+                runCurrent()
+
+                // Move flashlight tile to index 3
+                underTest.addTile(TileSpec.create("flashlight"), 3)
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec.spec })
+                    .containsExactly("airplane", "internet", "alarm", "flashlight")
+                    .inOrder()
+            }
+        }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun currentTiles_moveTileUp() =
+        with(kosmos) {
+            testScope.runTest {
+                val tiles by collectLastValue(underTest.tiles)
+                val currentTiles =
+                    mutableListOf(
+                        TileSpec.create("flashlight"),
+                        TileSpec.create("airplane"),
+                        TileSpec.create("internet"),
+                        TileSpec.create("alarm"),
+                    )
+                currentTilesInteractor.setTiles(currentTiles)
+                underTest.startEditing()
+                runCurrent()
+
+                // Move alarm tile to index 0
+                underTest.addTile(TileSpec.create("alarm"), 0)
+
+                assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec.spec })
+                    .containsExactly("alarm", "flashlight", "airplane", "internet")
+                    .inOrder()
+            }
+        }
 
     companion object {
         private val drawable1 = TestStubDrawable("drawable1")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index 19b8c66..62bfc72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import javax.inject.Named
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -37,22 +39,20 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import javax.inject.Inject
-import javax.inject.Named
 
 @SysUISingleton
 @OptIn(ExperimentalCoroutinesApi::class)
 class EditModeViewModel
 @Inject
 constructor(
-        private val editTilesListInteractor: EditTilesListInteractor,
-        private val currentTilesInteractor: CurrentTilesInteractor,
-        private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
-        private val minTilesInteractor: MinimumTilesInteractor,
-        @Named("Default") private val defaultGridLayout: GridLayout,
-        @Application private val applicationScope: CoroutineScope,
-        gridLayoutTypeInteractor: GridLayoutTypeInteractor,
-        gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
+    private val editTilesListInteractor: EditTilesListInteractor,
+    private val currentTilesInteractor: CurrentTilesInteractor,
+    private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
+    private val minTilesInteractor: MinimumTilesInteractor,
+    @Named("Default") private val defaultGridLayout: GridLayout,
+    @Application private val applicationScope: CoroutineScope,
+    gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+    gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
 ) {
     private val _isEditing = MutableStateFlow(false)
 
@@ -93,10 +93,12 @@
                 val editTilesData = editTilesListInteractor.getTilesToEdit()
                 // Query only the non current platform tiles, as any current tile is clearly
                 // available
-                val unavailable = tilesAvailabilityInteractor.getUnavailableTiles(
-                        editTilesData.stockTiles.map { it.tileSpec }
-                                .minus(currentTilesInteractor.currentTilesSpecs.toSet())
-                )
+                val unavailable =
+                    tilesAvailabilityInteractor.getUnavailableTiles(
+                        editTilesData.stockTiles
+                            .map { it.tileSpec }
+                            .minus(currentTilesInteractor.currentTilesSpecs.toSet())
+                    )
                 currentTilesInteractor.currentTiles.map { tiles ->
                     val currentSpecs = tiles.map { it.spec }
                     val canRemoveTiles = currentSpecs.size > minimumTiles
@@ -106,28 +108,28 @@
                     val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs }
 
                     (currentTiles + nonCurrentTiles)
-                            .filterNot { it.tileSpec in unavailable }
-                            .map {
-                                val current = it.tileSpec in currentSpecs
-                                val availableActions = buildSet {
-                                    if (current) {
-                                        add(AvailableEditActions.MOVE)
-                                        if (canRemoveTiles) {
-                                            add(AvailableEditActions.REMOVE)
-                                        }
-                                    } else {
-                                        add(AvailableEditActions.ADD)
+                        .filterNot { it.tileSpec in unavailable }
+                        .map {
+                            val current = it.tileSpec in currentSpecs
+                            val availableActions = buildSet {
+                                if (current) {
+                                    add(AvailableEditActions.MOVE)
+                                    if (canRemoveTiles) {
+                                        add(AvailableEditActions.REMOVE)
                                     }
+                                } else {
+                                    add(AvailableEditActions.ADD)
                                 }
-                                EditTileViewModel(
-                                        it.tileSpec,
-                                        it.icon,
-                                        it.label,
-                                        it.appName,
-                                        current,
-                                        availableActions
-                                )
                             }
+                            EditTileViewModel(
+                                it.tileSpec,
+                                it.icon,
+                                it.label,
+                                it.appName,
+                                current,
+                                availableActions
+                            )
+                        }
                 }
             } else {
                 emptyFlow()
@@ -144,13 +146,16 @@
         _isEditing.value = false
     }
 
-    /** Immediately moves [tileSpec] to [position]. */
-    fun moveTile(tileSpec: TileSpec, position: Int) {
-        throw NotImplementedError("This is not supported yet")
-    }
-
-    /** Immediately adds [tileSpec] to the current tiles at [position]. */
+    /**
+     * Immediately adds [tileSpec] to the current tiles at [position]. If the [tileSpec] was already
+     * present, it will be moved to the new position.
+     */
     fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
+        // Removing tile if it's already present to insert it at the new index.
+        if (currentTilesInteractor.currentTilesSpecs.contains(tileSpec)) {
+            removeTile(tileSpec)
+        }
+
         currentTilesInteractor.addTile(tileSpec, position)
     }