From e0797a75fce35b5adc4990c273c4bffccde49ed2 Mon Sep 17 00:00:00 2001 From: Owen McGirr Date: Sat, 4 Jul 2026 16:30:58 +0100 Subject: [PATCH] Clear PC drag after click commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clear mirrored drag state after successful click commands - Keep movement, scroll, and modifier commands from clearing drag - Add view-model coverage for drag clearing and failure behavior 🤖 Auto-generated --- .../screens/pc/PcMouseControlViewModel.kt | 8 +- .../screens/pc/PcMouseControlViewModelTest.kt | 101 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModel.kt b/app/src/main/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModel.kt index d421a4ea..405df677 100644 --- a/app/src/main/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModel.kt +++ b/app/src/main/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModel.kt @@ -142,7 +142,7 @@ class PcMouseControlViewModel( isDragging = when (commandToSend) { is PcControlCommand.DragStart -> true is PcControlCommand.DragEnd -> false - else -> it.isDragging + else -> if (it.isDragging && commandToSend.endsActiveDragWithClick()) false else it.isDragging }, isBusy = false, busyCommand = null, @@ -1134,6 +1134,12 @@ class PcMouseControlViewModel( } } +private fun PcControlCommand.endsActiveDragWithClick(): Boolean { + return this is PcControlCommand.LeftClick || + this is PcControlCommand.DoubleClick || + this is PcControlCommand.RightClick +} + private class InMemoryControlSurfaceStore : PcControlSurfaceStore { private var surface = PcControlSurface.Mouse diff --git a/app/src/test/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModelTest.kt b/app/src/test/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModelTest.kt index 45fa0969..d621f2d0 100644 --- a/app/src/test/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModelTest.kt +++ b/app/src/test/java/com/enaboapps/switchify/screens/pc/PcMouseControlViewModelTest.kt @@ -1469,6 +1469,107 @@ class PcMouseControlViewModelTest { assertEquals(PcMouseControlViewModel.COMMAND_FAILED_MESSAGE, viewModel.uiState.value.message) } + @Test + fun leftClickClearsDraggingAfterSuccessfulSend() = runTest(dispatcher) { + val connector = FakeConnector() + val controller = connectedController(connector = connector) + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.LeftClick) + advanceUntilIdle() + + assertEquals( + listOf(PcControlCommand.DragStart(), PcControlCommand.LeftClick), + connector.realtimeCommands + ) + assertFalse(viewModel.uiState.value.isDragging) + } + + @Test + fun rightClickClearsDraggingAfterSuccessfulSend() = runTest(dispatcher) { + val controller = connectedController() + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.RightClick) + advanceUntilIdle() + + assertFalse(viewModel.uiState.value.isDragging) + } + + @Test + fun doubleClickClearsDraggingAfterSuccessfulSend() = runTest(dispatcher) { + val controller = connectedController() + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.DoubleClick) + advanceUntilIdle() + + assertFalse(viewModel.uiState.value.isDragging) + } + + @Test + fun failedClickDoesNotClearDraggingMirror() = runTest(dispatcher) { + val connector = FakeConnector( + realtimeResults = mutableListOf(PcCommandResult.Ack, PcCommandResult.Failed()) + ) + val controller = connectedController(connector = connector) + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.LeftClick) + advanceUntilIdle() + + assertTrue(viewModel.uiState.value.isDragging) + assertEquals(PcMouseControlViewModel.COMMAND_FAILED_MESSAGE, viewModel.uiState.value.message) + } + + @Test + fun moveDoesNotClearDragging() = runTest(dispatcher) { + val controller = connectedController() + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.Move(dx = 10, dy = 0)) + advanceUntilIdle() + + assertTrue(viewModel.uiState.value.isDragging) + } + + @Test + fun scrollDoesNotClearDragging() = runTest(dispatcher) { + val controller = connectedController() + val viewModel = viewModel(controller) + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.send(PcControlCommand.Scroll(dx = 0, dy = -1)) + advanceUntilIdle() + + assertTrue(viewModel.uiState.value.isDragging) + } + + @Test + fun modifierToggleDoesNotClearDragging() = runTest(dispatcher) { + val controller = connectedController(pointerProfile = modifierPointerProfile()) + val viewModel = viewModel(controller) + advanceUntilIdle() + + viewModel.send(PcControlCommand.DragStart()) + advanceUntilIdle() + viewModel.toggleModifier(PcKeyboardModifierKey.Ctrl) + advanceUntilIdle() + + assertTrue(viewModel.uiState.value.isDragging) + } + @Test fun supportedProfileEnablesModifierToggles() = runTest(dispatcher) { val controller = connectedController(pointerProfile = modifierPointerProfile())