Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class PreferencesManager @Inject constructor(@param:ApplicationContext private v
*/
private val DRAWER_SEARCH_AUTO_LAUNCH_KEY =
booleanPreferencesKey("drawer_search_auto_launch")
private val DRAWER_SCROLL_TO_TOP_AUTO_KEYBOARD_KEY =
booleanPreferencesKey("drawer_scroll_to_top_auto_keyboard")
private val HAS_COMPLETED_ONBOARDING_KEY = booleanPreferencesKey("has_completed_onboarding")
private val ONBOARDING_REACHED_SET_DEFAULT_KEY = booleanPreferencesKey("onboarding_reached_set_default")
/**
Expand Down Expand Up @@ -502,6 +504,11 @@ class PreferencesManager @Inject constructor(@param:ApplicationContext private v
suspend fun setDrawerSearchAutoLaunch(enabled: Boolean) =
setPref(DRAWER_SEARCH_AUTO_LAUNCH_KEY, enabled)

val drawerScrollToTopAutoKeyboardFlow: Flow<Boolean> =
prefFlow(DRAWER_SCROLL_TO_TOP_AUTO_KEYBOARD_KEY, false)
suspend fun setDrawerScrollToTopAutoKeyboard(enabled: Boolean) =
setPref(DRAWER_SCROLL_TO_TOP_AUTO_KEYBOARD_KEY, enabled)

val drawerAppOpenCountsFlow: Flow<Map<String, Int>> =
context.fokusLauncherPreferencesDataStore.data.map { prefs ->
parseDrawerOpenCounts(prefs[DRAWER_APP_OPEN_COUNTS_KEY] ?: "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
Expand All @@ -57,6 +58,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.delay
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
Expand Down Expand Up @@ -827,35 +829,37 @@ fun AppDrawerContent(

BackHandler { closeWithFocusReset() }

LaunchedEffect(useSidebarCategoryDrawer, showSearch) {
val wantKeyboard =
if (useSidebarCategoryDrawer) showSearch else true
if (!wantKeyboard) return@LaunchedEffect
focusRequester.requestFocus()
keyboardController?.show()
val isAtTop by remember(listState) {
derivedStateOf { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }
}
var hasScrolledDown by remember { mutableStateOf(false) }

LaunchedEffect(listState, keyboardController, focusManager) {
var prevIndex = listState.firstVisibleItemIndex
var prevOffset = listState.firstVisibleItemScrollOffset
snapshotFlow {
Triple(
listState.isScrollInProgress,
listState.firstVisibleItemIndex,
listState.firstVisibleItemScrollOffset
)
}.collect { (scrolling, index, offset) ->
if (scrolling) {
val scrolledDown =
index > prevIndex ||
(index == prevIndex && offset > prevOffset)
if (scrolledDown) {
keyboardController?.hide()
focusManager.clearFocus(force = true)
}
// Unified Search/Keyboard management: Handles entry, scroll-to-top, and pull-to-open
LaunchedEffect(isAtTop, showSearch, useSidebarCategoryDrawer, uiState.drawerScrollToTopAutoKeyboard) {
// Trigger keyboard if:
// 1. Initial entry or setting is on, AND we are at the top
// 2. Search is explicitly toggled on in sidebar mode
val topAutoLaunch = isAtTop && (!hasScrolledDown || uiState.drawerScrollToTopAutoKeyboard)

if (topAutoLaunch) {
if (useSidebarCategoryDrawer && !showSearch) {
showSearch = true
} else {
delay(100)
focusRequester.requestFocus()
keyboardController?.show()
}
prevIndex = index
prevOffset = offset
} else if (useSidebarCategoryDrawer && showSearch) {
// Focus if search was explicitly toggled
delay(100)
focusRequester.requestFocus()
keyboardController?.show()
} else if (!isAtTop) {
// Track that we've left the top once
hasScrolledDown = true
keyboardController?.hide()
focusManager.clearFocus(force = true)
if (useSidebarCategoryDrawer) showSearch = false
}
}

Expand All @@ -864,6 +868,15 @@ fun AppDrawerContent(
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (source == NestedScrollSource.UserInput && available.y > 0 && !listState.canScrollBackward) {
// Immediate response for pull-down gesture at the boundary
if (uiState.drawerScrollToTopAutoKeyboard) {
// The LaunchedEffect(isAtTop) handles the actual focus/keyboard
// but if we are already atTop, it won't re-trigger.
// So we force a request here if already at top and pulling.
if (useSidebarCategoryDrawer && !showSearch) showSearch = true
focusRequester.requestFocus()
keyboardController?.show()
}
overscrollY += available.y
if (overscrollY > 300f) {
overscrollY = 0f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ data class AppDrawerUiState(
val categoryDrawerIconOverrides: Map<String, String> = emptyMap(),
val usesPhotoWallpaper: Boolean = false,
val drawerAppSortMode: DrawerAppSortMode = DrawerAppSortMode.ALPHABETICAL,
/** Automatically open keyboard when scrolling to the top of the app drawer. */
val drawerScrollToTopAutoKeyboard: Boolean = false,
/**
* When true with [drawerAppSortMode] CUSTOM and sidebar layout, the list shows drag handles and
* can be reordered. Cleared when the drawer closes, search filters, or CUSTOM layout is unavailable.
Expand Down Expand Up @@ -277,6 +279,7 @@ constructor(
observeDrawerCategoryRailAndIcons()
observeDrawerSortOpenCountsAndCustomOrder()
observeDrawerDotSearchPreferences()
observeDrawerScrollToTopAutoKeyboard()
observeLauncherAppearance()
observeDrawerSearchAutoLaunch()
refreshPrivateSpaceState()
Expand Down Expand Up @@ -312,6 +315,14 @@ constructor(
}
}

private fun observeDrawerScrollToTopAutoKeyboard() {
viewModelScope.launch {
preferencesManager.drawerScrollToTopAutoKeyboardFlow.collect { enabled ->
_uiState.update { it.copy(drawerScrollToTopAutoKeyboard = enabled) }
}
}
}

/**
* Loads profile-section and private-app sort caches for the current [AppDrawerUiState] without
* publishing UI. Schedules work on [viewModelScope] so the drawer’s first open avoids cold CPU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,14 @@ private fun SettingsScreenContent(
onCheckedChange = viewModel::setDrawerSearchAutoLaunch
)
}
item {
SettingsToggleRow(
label = stringResource(R.string.settings_drawer_scroll_to_top_auto_keyboard),
subtitle = stringResource(R.string.settings_drawer_scroll_to_top_auto_keyboard_subtitle),
checked = uiState.drawerScrollToTopAutoKeyboard,
onCheckedChange = viewModel::setDrawerScrollToTopAutoKeyboard
)
}
item {
SettingsToggleRow(
label = stringResource(R.string.settings_drawer_sidebar_categories),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ data class SettingsUiState(
val drawerSidebarCategories: Boolean = false,
/** Launch the app when search narrows to a single match (drawer search). */
val drawerSearchAutoLaunch: Boolean = true,
/** Auto-launch keyboard when scrolling to the top of the app drawer. */
val drawerScrollToTopAutoKeyboard: Boolean = false,
/** When true, category rail is on the left; default false places it on the right. */
val drawerCategorySidebarOnLeft: Boolean = false,
/** Normalized category key → [MinimalIcons] name for the drawer sidebar rail. */
Expand Down Expand Up @@ -226,17 +228,23 @@ constructor(
preferencesManager.drawerSidebarCategoriesFlow,
preferencesManager.drawerAppSortModeFlow,
preferencesManager.drawerSearchAutoLaunchFlow,
) { sidebarCategories, sortMode, searchAutoLaunch ->
Triple(sidebarCategories, sortMode, searchAutoLaunch)
preferencesManager.drawerScrollToTopAutoKeyboardFlow,
) { sidebarCategories, sortMode, searchAutoLaunch, scrollToTopAutoKeyboard ->
DrawerPrefs(
swipeRightTarget = null, // placeholder
preferredWeatherAppPackage = "", // placeholder
showStatusBar = false, // placeholder
drawerSidebarCategories = sidebarCategories,
drawerAppSortMode = sortMode,
drawerSearchAutoLaunch = searchAutoLaunch,
drawerScrollToTopAutoKeyboard = scrollToTopAutoKeyboard,
)
},
) { swipeAndWeather, drawerLayout ->
DrawerPrefs(
drawerLayout.copy(
swipeRightTarget = swipeAndWeather.first,
preferredWeatherAppPackage = swipeAndWeather.second,
showStatusBar = swipeAndWeather.third,
drawerSidebarCategories = drawerLayout.first,
drawerAppSortMode = drawerLayout.second,
drawerSearchAutoLaunch = drawerLayout.third,
)
}
val fontVisualFlow =
Expand Down Expand Up @@ -365,6 +373,7 @@ constructor(
temperatureUnit = homeWidgetItems.temperatureUnit,
drawerSidebarCategories = drawer.drawerSidebarCategories,
drawerSearchAutoLaunch = drawer.drawerSearchAutoLaunch,
drawerScrollToTopAutoKeyboard = drawer.drawerScrollToTopAutoKeyboard,
drawerCategorySidebarOnLeft = lockRail.drawerCategorySidebarOnLeft,
categoryDrawerIconOverrides = lockRail.categoryDrawerIconOverrides,
drawerAppSortMode = drawer.drawerAppSortMode,
Expand Down Expand Up @@ -423,6 +432,7 @@ constructor(
val drawerSidebarCategories: Boolean,
val drawerAppSortMode: DrawerAppSortMode,
val drawerSearchAutoLaunch: Boolean,
val drawerScrollToTopAutoKeyboard: Boolean,
)

private data class FontVisualPrefs(
Expand Down Expand Up @@ -662,6 +672,9 @@ constructor(
fun setDrawerSearchAutoLaunch(enabled: Boolean) =
launchPreferences { setDrawerSearchAutoLaunch(enabled) }

fun setDrawerScrollToTopAutoKeyboard(enabled: Boolean) =
launchPreferences { setDrawerScrollToTopAutoKeyboard(enabled) }

fun setDrawerCategorySidebarOnLeft(onLeft: Boolean) =
launchPreferences { setDrawerCategorySidebarOnLeft(onLeft) }

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,4 +349,8 @@
<string name="settings_weblate_subtitle">Contribute translations on Weblate</string>
<string name="settings_matrix_title">Join the discussion</string>
<string name="settings_matrix_subtitle">Get help and share feedback</string>

<!-- Settings - App drawer -->
<string name="settings_drawer_scroll_to_top_auto_keyboard">Keyboard on scroll to top</string>
<string name="settings_drawer_scroll_to_top_auto_keyboard_subtitle">Automatically open keyboard and search when scrolling to the top of the app list</string>
</resources>
Loading