feat(profiles): Add drag-to-reorder for profile list

Wire up the missing drag-to-reorder UI that the priority hint already advertises. Long-press a profile row to reorder. Extracts a reusable ReorderableState component into common/compose.

Backend (repo + ViewModel) was already in place; this adds the gesture handling, auto-scroll, visual feedback, and ID-based reorder validation.
This commit is contained in:
darken
2026-04-02 18:17:00 +02:00
parent 4a475419e8
commit 1f5f406b33
5 changed files with 338 additions and 14 deletions
@@ -0,0 +1,81 @@
package eu.darken.capod.profiles.core
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.flowOf
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.coroutine.runTest2
import testhelpers.datastore.FakeDataStoreValue
class DeviceProfilesRepoReorderTest : BaseTest() {
private fun createRepo(
initialProfiles: List<DeviceProfile>,
scope: CoroutineScope,
): Pair<DeviceProfilesRepo, FakeDataStoreValue<DeviceProfilesContainer>> {
val fakeProfiles = FakeDataStoreValue(DeviceProfilesContainer(initialProfiles))
val settings = mockk<DeviceProfilesSettings> {
every { profiles } returns fakeProfiles.mock
every { defaultProfileCreated } returns FakeDataStoreValue(true).mock
}
val repo = DeviceProfilesRepo(
scope = scope,
context = mockk(relaxed = true),
generalSettings = mockk(relaxed = true),
settings = settings,
deviceStateCache = mockk(relaxed = true),
)
return repo to fakeProfiles
}
@Test
fun `reorderProfilesById reorders profiles correctly`() = runTest2 {
val p1 = AppleDeviceProfile(id = "1", label = "First")
val p2 = AppleDeviceProfile(id = "2", label = "Second")
val p3 = AppleDeviceProfile(id = "3", label = "Third")
val (repo, fakeProfiles) = createRepo(listOf(p1, p2, p3), this)
repo.reorderProfilesById(listOf("3", "1", "2"))
fakeProfiles.value.profiles.map { it.id } shouldBe listOf("3", "1", "2")
}
@Test
fun `reorderProfilesById preserves profile data`() = runTest2 {
val p1 = AppleDeviceProfile(id = "1", label = "First", minimumSignalQuality = 0.5f)
val p2 = AppleDeviceProfile(id = "2", label = "Second", minimumSignalQuality = 0.8f)
val (repo, fakeProfiles) = createRepo(listOf(p1, p2), this)
repo.reorderProfilesById(listOf("2", "1"))
fakeProfiles.value.profiles shouldBe listOf(p2, p1)
}
@Test
fun `reorderProfilesById rejects mismatched IDs`() = runTest2(expectedError = IllegalArgumentException::class) {
val p1 = AppleDeviceProfile(id = "1", label = "First")
val p2 = AppleDeviceProfile(id = "2", label = "Second")
val (repo, _) = createRepo(listOf(p1, p2), this)
repo.reorderProfilesById(listOf("1", "3"))
}
@Test
fun `reorderProfilesById rejects wrong count`() = runTest2(expectedError = IllegalArgumentException::class) {
val p1 = AppleDeviceProfile(id = "1", label = "First")
val p2 = AppleDeviceProfile(id = "2", label = "Second")
val (repo, _) = createRepo(listOf(p1, p2), this)
repo.reorderProfilesById(listOf("1"))
}
}