From f051c38b7efd91cad5628edc1354db5ee8484685 Mon Sep 17 00:00:00 2001 From: manish Date: Mon, 27 Apr 2026 12:28:32 +0530 Subject: [PATCH 1/2] Fix: Double tap to lock now works on all empty space. Moved the double-tap gesture listener from a specific middle band to the root container. This ensures that tapping any unoccupied area (margins, padding, or gaps between widgets) triggers the lock, while still allowing widgets and icons to handle their own interactions. --- .../lu4p/fokuslauncher/ui/home/HomeScreen.kt | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/lu4p/fokuslauncher/ui/home/HomeScreen.kt b/app/src/main/java/com/lu4p/fokuslauncher/ui/home/HomeScreen.kt index 334ef33e..47f2bf38 100644 --- a/app/src/main/java/com/lu4p/fokuslauncher/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/lu4p/fokuslauncher/ui/home/HomeScreen.kt @@ -215,7 +215,13 @@ fun HomeScreenContent( indication = null, interactionSource = noIndication, onClick = { }, - onLongClick = onHomeScreenLongPress + onLongClick = onHomeScreenLongPress, + onDoubleClick = if (doubleTapEmptyLockEnabled) { + { + play() + onDoubleTapEmptyLock() + } + } else null ) .testTag("home_screen") ) { @@ -238,27 +244,7 @@ fun HomeScreenContent( outlined = uiState.usesPhotoWallpaper, ) - // Push favorites to the bottom; optional double-tap to lock on this empty band - if (doubleTapEmptyLockEnabled) { - val emptyTapSource = remember { MutableInteractionSource() } - Box( - modifier = - Modifier.weight(1f) - .fillMaxWidth() - .combinedClickable( - indication = null, - interactionSource = emptyTapSource, - onClick = {}, - onLongClick = onHomeScreenLongPress, - onDoubleClick = { - play() - onDoubleTapEmptyLock() - }, - ) - ) - } else { - Spacer(modifier = Modifier.weight(1f)) - } + Spacer(modifier = Modifier.weight(1f)) HomeFavoritesSection( homeAlignment = uiState.homeAlignment, From 0280a43b17d35bdb085a08318b5823058d3b0517 Mon Sep 17 00:00:00 2001 From: manish Date: Mon, 27 Apr 2026 12:40:04 +0530 Subject: [PATCH 2/2] Added a unit test in HomeViewModelTest to verify the lock trigger logic. --- .../fokuslauncher/ui/home/HomeScreenTest.kt | 79 +++++++++++++++++++ .../ui/home/HomeViewModelTest.kt | 18 +++++ 2 files changed, 97 insertions(+) diff --git a/app/src/androidTest/java/com/lu4p/fokuslauncher/ui/home/HomeScreenTest.kt b/app/src/androidTest/java/com/lu4p/fokuslauncher/ui/home/HomeScreenTest.kt index 99766498..d8be7a36 100644 --- a/app/src/androidTest/java/com/lu4p/fokuslauncher/ui/home/HomeScreenTest.kt +++ b/app/src/androidTest/java/com/lu4p/fokuslauncher/ui/home/HomeScreenTest.kt @@ -5,13 +5,16 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.doubleClick import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput import com.lu4p.fokuslauncher.data.model.FavoriteApp import com.lu4p.fokuslauncher.data.model.HomeShortcut import com.lu4p.fokuslauncher.data.model.WeatherData import com.lu4p.fokuslauncher.ui.theme.FokusLauncherTheme +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test @@ -311,4 +314,80 @@ class HomeScreenTest { assertTrue(bottomShortcut >= bottomFavorite - 1f) } + + @Test + fun homeScreen_doubleTapToLock_triggersCallback() { + var doubleTapTriggered = false + composeTestRule.setContent { + FokusLauncherTheme { + HomeScreenContent( + uiState = HomeUiState(doubleTapEmptyLockEnabled = true), + clockUiState = clock(), + weatherUiState = weatherOff, + favorites = testFavorites, + rightSideShortcuts = testRightSideShortcuts, + onLabelClick = {}, + onLabelLongPress = {}, + onIconClick = {}, + onDoubleTapEmptyLock = { doubleTapTriggered = true } + ) + } + } + + composeTestRule.onNodeWithTag("home_screen").performTouchInput { + doubleClick() + } + assertTrue(doubleTapTriggered) + } + + @Test + fun homeScreen_doubleTapToLockDisabled_doesNotTriggerCallback() { + var doubleTapTriggered = false + composeTestRule.setContent { + FokusLauncherTheme { + HomeScreenContent( + uiState = HomeUiState(doubleTapEmptyLockEnabled = false), + clockUiState = clock(), + weatherUiState = weatherOff, + favorites = testFavorites, + rightSideShortcuts = testRightSideShortcuts, + onLabelClick = {}, + onLabelLongPress = {}, + onIconClick = {}, + onDoubleTapEmptyLock = { doubleTapTriggered = true } + ) + } + } + + composeTestRule.onNodeWithTag("home_screen").performTouchInput { + doubleClick() + } + assertFalse(doubleTapTriggered) + } + + @Test + fun homeScreen_doubleTapOnFavorite_doesNotTriggerLock() { + var doubleTapTriggered = false + composeTestRule.setContent { + FokusLauncherTheme { + HomeScreenContent( + uiState = HomeUiState(doubleTapEmptyLockEnabled = true), + clockUiState = clock(), + weatherUiState = weatherOff, + favorites = testFavorites, + rightSideShortcuts = testRightSideShortcuts, + onLabelClick = {}, + onLabelLongPress = {}, + onIconClick = {}, + onDoubleTapEmptyLock = { doubleTapTriggered = true } + ) + } + } + + composeTestRule.onNodeWithText("Music").performTouchInput { + doubleClick() + } + + assertFalse("Double tap should not be triggered on favorite", doubleTapTriggered) + } } diff --git a/app/src/test/java/com/lu4p/fokuslauncher/ui/home/HomeViewModelTest.kt b/app/src/test/java/com/lu4p/fokuslauncher/ui/home/HomeViewModelTest.kt index 67d59084..52205a51 100644 --- a/app/src/test/java/com/lu4p/fokuslauncher/ui/home/HomeViewModelTest.kt +++ b/app/src/test/java/com/lu4p/fokuslauncher/ui/home/HomeViewModelTest.kt @@ -25,10 +25,13 @@ import com.lu4p.fokuslauncher.data.model.appMetadataKey import com.lu4p.fokuslauncher.data.repository.AppRepository import com.lu4p.fokuslauncher.data.repository.RemovedApp import com.lu4p.fokuslauncher.data.repository.WeatherRepository +import com.lu4p.fokuslauncher.utils.LockScreenHelper import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject import io.mockk.verify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -655,4 +658,19 @@ class HomeViewModelTest { verify { appRepository.launchApp("com.lu4p.music") } } + + @Test + fun `onDoubleTapEmptyLock calls lockScreenIfPossible when enabled`() { + mockkObject(LockScreenHelper) + every { LockScreenHelper.isLockAccessibilityServiceEnabled(any()) } returns true + every { LockScreenHelper.lockScreenIfPossible() } returns true + every { preferencesManager.doubleTapEmptyLockFlow } returns flowOf(true) + + val viewModel = createViewModel() + viewModel.onDoubleTapEmptyLock() + testDispatcher.scheduler.runCurrent() + + verify { LockScreenHelper.lockScreenIfPossible() } + unmockkObject(LockScreenHelper) + } }