Fix keyboard filter bar placement issues
This commit is contained in:
parent
9b29696757
commit
eda57aea2b
@ -41,6 +41,7 @@ fun AssistantScaffold(
|
|||||||
fixedSearchBar: Boolean = false,
|
fixedSearchBar: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val viewModel: LauncherScaffoldVM = viewModel()
|
val viewModel: LauncherScaffoldVM = viewModel()
|
||||||
|
val searchVM: SearchVM = viewModel()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -60,6 +61,10 @@ fun AssistantScaffold(
|
|||||||
|
|
||||||
val searchState = rememberLazyListState()
|
val searchState = rememberLazyListState()
|
||||||
|
|
||||||
|
val keyboardFilterBarPadding by animateDpAsState(
|
||||||
|
if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
|
||||||
|
)
|
||||||
|
|
||||||
val isSearchAtStart by remember {
|
val isSearchAtStart by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
|
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
|
||||||
@ -94,6 +99,7 @@ fun AssistantScaffold(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val showNavBarScrim by remember {
|
val showNavBarScrim by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (reverseSearchResults) {
|
if (reverseSearchResults) {
|
||||||
@ -148,8 +154,6 @@ fun AssistantScaffold(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchVM: SearchVM = viewModel()
|
|
||||||
val actions by searchVM.searchActionResults
|
val actions by searchVM.searchActionResults
|
||||||
val webSearchPadding by animateDpAsState(
|
val webSearchPadding by animateDpAsState(
|
||||||
if (actions.isEmpty()) 0.dp else 48.dp
|
if (actions.isEmpty()) 0.dp else 48.dp
|
||||||
@ -164,7 +168,7 @@ fun AssistantScaffold(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
paddingValues = PaddingValues(
|
paddingValues = PaddingValues(
|
||||||
top = (if (bottomSearchBar) 0.dp else 56.dp + webSearchPadding) + 4.dp + windowInsets.calculateTopPadding(),
|
top = (if (bottomSearchBar) 0.dp else 56.dp + webSearchPadding) + 4.dp + windowInsets.calculateTopPadding(),
|
||||||
bottom = (if (bottomSearchBar) 56.dp + webSearchPadding else 0.dp) + 4.dp + windowInsets.calculateBottomPadding()
|
bottom = (if (bottomSearchBar) 56.dp + webSearchPadding else 0.dp) + 4.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
||||||
),
|
),
|
||||||
reverse = reverseSearchResults,
|
reverse = reverseSearchResults,
|
||||||
state = searchState
|
state = searchState
|
||||||
@ -179,18 +183,12 @@ fun AssistantScaffold(
|
|||||||
|
|
||||||
LauncherSearchBar(
|
LauncherSearchBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize(),
|
||||||
.wrapContentHeight()
|
searchBarOffset = {
|
||||||
.align(if (bottomSearchBar) Alignment.BottomCenter else Alignment.TopCenter)
|
(if (searchBarFocused || fixedSearchBar) 0
|
||||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
else searchBarOffset.toInt() * if (bottomSearchBar) -1 else 1)
|
||||||
.padding(8.dp)
|
- (if (bottomSearchBar) with(density) { keyboardFilterBarPadding.toPx() }.toInt() else 0)
|
||||||
.offset {
|
},
|
||||||
if (searchBarFocused || fixedSearchBar) IntOffset.Zero
|
|
||||||
else IntOffset(
|
|
||||||
0,
|
|
||||||
searchBarOffset.toInt() * if (bottomSearchBar) -1 else 1
|
|
||||||
)
|
|
||||||
},
|
|
||||||
level = { searchBarLevel },
|
level = { searchBarLevel },
|
||||||
focused = searchBarFocused,
|
focused = searchBarFocused,
|
||||||
onFocusChange = {
|
onFocusChange = {
|
||||||
@ -204,7 +202,7 @@ fun AssistantScaffold(
|
|||||||
onValueChange = { searchVM.search(it) },
|
onValueChange = { searchVM.search(it) },
|
||||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||||
style = searchBarStyle,
|
style = searchBarStyle,
|
||||||
reverse = bottomSearchBar,
|
bottomSearchBar = bottomSearchBar,
|
||||||
onKeyboardActionGo = if (launchOnEnter) {
|
onKeyboardActionGo = if (launchOnEnter) {
|
||||||
{ searchVM.launchBestMatchOrAction(context) }
|
{ searchVM.launchBestMatchOrAction(context) }
|
||||||
} else null
|
} else null
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.imeAnimationTarget
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredWidth
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
@ -131,6 +131,10 @@ fun PagerScaffold(
|
|||||||
|
|
||||||
val pagerState = rememberPagerState { 2 }
|
val pagerState = rememberPagerState { 2 }
|
||||||
|
|
||||||
|
val keyboardFilterBarPadding by animateDpAsState(
|
||||||
|
if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
|
||||||
|
)
|
||||||
|
|
||||||
val isSearchAtBottom by remember {
|
val isSearchAtBottom by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (reverseSearchResults) {
|
if (reverseSearchResults) {
|
||||||
@ -511,11 +515,11 @@ fun PagerScaffold(
|
|||||||
val paddingValues = if (bottomSearchBar) {
|
val paddingValues = if (bottomSearchBar) {
|
||||||
PaddingValues(
|
PaddingValues(
|
||||||
top = 4.dp + windowInsets.calculateTopPadding(),
|
top = 4.dp + windowInsets.calculateTopPadding(),
|
||||||
bottom = 60.dp + webSearchPadding + windowInsets.calculateBottomPadding()
|
bottom = 60.dp + webSearchPadding + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
PaddingValues(
|
PaddingValues(
|
||||||
bottom = 4.dp + windowInsets.calculateBottomPadding(),
|
bottom = 4.dp + windowInsets.calculateBottomPadding() + keyboardFilterBarPadding,
|
||||||
top = 60.dp + webSearchPadding + windowInsets.calculateTopPadding()
|
top = 60.dp + webSearchPadding + windowInsets.calculateTopPadding()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -597,27 +601,17 @@ fun PagerScaffold(
|
|||||||
|
|
||||||
LauncherSearchBar(
|
LauncherSearchBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(if (bottomSearchBar) Alignment.BottomCenter else Alignment.TopCenter)
|
.fillMaxSize(),
|
||||||
.fillMaxWidth()
|
level = { searchBarLevel },
|
||||||
.wrapContentHeight()
|
searchBarOffset = {
|
||||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
(if (focusSearchBar || fixedSearchBar) 0 else searchBarOffset.value.toInt() * if (bottomSearchBar) 1 else -1) +
|
||||||
.padding(8.dp)
|
|
||||||
.offset {
|
|
||||||
IntOffset(
|
|
||||||
0,
|
|
||||||
if (focusSearchBar || fixedSearchBar) 0 else searchBarOffset.value.toInt() * if (bottomSearchBar) 1 else -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.offset {
|
|
||||||
IntOffset(
|
|
||||||
0,
|
|
||||||
with(density) {
|
with(density) {
|
||||||
widgetEditModeOffset
|
(widgetEditModeOffset - if (bottomSearchBar) keyboardFilterBarPadding else 0.dp)
|
||||||
.toPx()
|
.toPx()
|
||||||
.roundToInt()
|
.roundToInt()
|
||||||
})
|
}
|
||||||
},
|
},
|
||||||
level = { searchBarLevel },
|
bottomSearchBar = bottomSearchBar,
|
||||||
focused = focusSearchBar,
|
focused = focusSearchBar,
|
||||||
onFocusChange = {
|
onFocusChange = {
|
||||||
if (it) viewModel.openSearch()
|
if (it) viewModel.openSearch()
|
||||||
@ -630,7 +624,6 @@ fun PagerScaffold(
|
|||||||
onValueChange = { searchVM.search(it) },
|
onValueChange = { searchVM.search(it) },
|
||||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||||
style = searchBarStyle,
|
style = searchBarStyle,
|
||||||
reverse = bottomSearchBar,
|
|
||||||
onKeyboardActionGo = if (launchOnEnter) {
|
onKeyboardActionGo = if (launchOnEnter) {
|
||||||
{ searchVM.launchBestMatchOrAction(context) }
|
{ searchVM.launchBestMatchOrAction(context) }
|
||||||
} else null
|
} else null
|
||||||
@ -702,7 +695,8 @@ fun Modifier.pagerScaffoldScrollHandler(
|
|||||||
val available = dragAmount - preConsumed
|
val available = dragAmount - preConsumed
|
||||||
val consumedY =
|
val consumedY =
|
||||||
scrollableState.scrollBy(available.y * scrollMultiplier) * scrollMultiplier
|
scrollableState.scrollBy(available.y * scrollMultiplier) * scrollMultiplier
|
||||||
val consumedX = pagerState.scrollBy(available.x * pagerMultiplier) * pagerMultiplier
|
val consumedX =
|
||||||
|
pagerState.scrollBy(available.x * pagerMultiplier) * pagerMultiplier
|
||||||
val totalConsumed =
|
val totalConsumed =
|
||||||
Offset(preConsumed.x + consumedX, preConsumed.y + consumedY)
|
Offset(preConsumed.x + consumedX, preConsumed.y + consumedY)
|
||||||
nestedScrollDispatcher.dispatchPostScroll(
|
nestedScrollDispatcher.dispatchPostScroll(
|
||||||
|
|||||||
@ -21,11 +21,11 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imeAnimationTarget
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawing
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.pager.VerticalPager
|
import androidx.compose.foundation.pager.VerticalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
@ -116,6 +116,10 @@ fun PullDownScaffold(
|
|||||||
val maxOffset = with(density) { 64.dp.toPx() }
|
val maxOffset = with(density) { 64.dp.toPx() }
|
||||||
val toggleSearchThreshold = with(density) { 48.dp.toPx() }
|
val toggleSearchThreshold = with(density) { 48.dp.toPx() }
|
||||||
|
|
||||||
|
val keyboardFilterBarPadding by animateDpAsState(
|
||||||
|
if (WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0 && !searchVM.showFilters.value) 50.dp else 0.dp
|
||||||
|
)
|
||||||
|
|
||||||
val isSearchAtTop by remember {
|
val isSearchAtTop by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (reverseSearchResults) {
|
if (reverseSearchResults) {
|
||||||
@ -300,9 +304,7 @@ fun PullDownScaffold(
|
|||||||
handleBackOrHomeEvent()
|
handleBackOrHomeEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val gestureManager = LocalGestureDetector.current
|
val gestureManager = LocalGestureDetector.current
|
||||||
val hapticFeedback = LocalHapticFeedback.current
|
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
|
||||||
LaunchedEffect(isOverThreshold) {
|
LaunchedEffect(isOverThreshold) {
|
||||||
@ -517,7 +519,9 @@ fun PullDownScaffold(
|
|||||||
),
|
),
|
||||||
paddingValues = PaddingValues(
|
paddingValues = PaddingValues(
|
||||||
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 60.dp + webSearchPadding else 4.dp,
|
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 60.dp + webSearchPadding else 4.dp,
|
||||||
bottom = windowInsets.calculateBottomPadding() + if (bottomSearchBar) 60.dp + webSearchPadding else 4.dp
|
bottom = windowInsets.calculateBottomPadding() +
|
||||||
|
keyboardFilterBarPadding +
|
||||||
|
if (bottomSearchBar) 60.dp + webSearchPadding else 4.dp
|
||||||
),
|
),
|
||||||
state = searchState,
|
state = searchState,
|
||||||
reverse = reverseSearchResults,
|
reverse = reverseSearchResults,
|
||||||
@ -575,32 +579,21 @@ fun PullDownScaffold(
|
|||||||
|
|
||||||
LauncherSearchBar(
|
LauncherSearchBar(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(if (bottomSearchBar) Alignment.BottomCenter else Alignment.TopCenter)
|
.fillMaxSize(),
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
|
||||||
.padding(8.dp)
|
|
||||||
.offset {
|
|
||||||
IntOffset(
|
|
||||||
0,
|
|
||||||
if (searchBarFocused || fixedSearchBar) 0 else searchBarOffset.value.toInt() * (if (bottomSearchBar) 1 else -1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.offset {
|
|
||||||
IntOffset(
|
|
||||||
0,
|
|
||||||
with(density) {
|
|
||||||
editModeSearchBarOffset
|
|
||||||
.toPx()
|
|
||||||
.roundToInt()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
level = { searchBarLevel },
|
level = { searchBarLevel },
|
||||||
focused = searchBarFocused,
|
focused = searchBarFocused,
|
||||||
onFocusChange = {
|
onFocusChange = {
|
||||||
if (it) viewModel.openSearch()
|
if (it) viewModel.openSearch()
|
||||||
viewModel.setSearchbarFocus(it)
|
viewModel.setSearchbarFocus(it)
|
||||||
},
|
},
|
||||||
|
searchBarOffset = {
|
||||||
|
(if (searchBarFocused || fixedSearchBar) 0 else searchBarOffset.value.toInt() * (if (bottomSearchBar) 1 else -1)) +
|
||||||
|
with(density) {
|
||||||
|
(editModeSearchBarOffset - if(bottomSearchBar) keyboardFilterBarPadding else 0.dp)
|
||||||
|
.toPx()
|
||||||
|
.roundToInt()
|
||||||
|
}
|
||||||
|
},
|
||||||
actions = actions,
|
actions = actions,
|
||||||
highlightedAction = searchVM.bestMatch.value as? SearchAction,
|
highlightedAction = searchVM.bestMatch.value as? SearchAction,
|
||||||
isSearchOpen = isSearchOpen,
|
isSearchOpen = isSearchOpen,
|
||||||
@ -608,7 +601,7 @@ fun PullDownScaffold(
|
|||||||
onValueChange = { searchVM.search(it) },
|
onValueChange = { searchVM.search(it) },
|
||||||
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
darkColors = LocalPreferDarkContentOverWallpaper.current && searchBarColor == SearchBarColors.Auto || searchBarColor == SearchBarColors.Dark,
|
||||||
style = searchBarStyle,
|
style = searchBarStyle,
|
||||||
reverse = bottomSearchBar,
|
bottomSearchBar = bottomSearchBar,
|
||||||
onKeyboardActionGo = if (launchOnEnter) {
|
onKeyboardActionGo = if (launchOnEnter) {
|
||||||
{ searchVM.launchBestMatchOrAction(context) }
|
{ searchVM.launchBestMatchOrAction(context) }
|
||||||
} else null
|
} else null
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.launcher.search.filters
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -38,202 +39,197 @@ import androidx.compose.ui.unit.dp
|
|||||||
import de.mm20.launcher2.search.SearchFilters
|
import de.mm20.launcher2.search.SearchFilters
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.icons.Wikipedia
|
import de.mm20.launcher2.ui.icons.Wikipedia
|
||||||
import de.mm20.launcher2.ui.overlays.Overlay
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun KeyboardFilterBar(filters: SearchFilters, onFiltersChange: (SearchFilters) -> Unit) {
|
fun KeyboardFilterBar(filters: SearchFilters, onFiltersChange: (SearchFilters) -> Unit) {
|
||||||
Overlay {
|
val allCategoriesEnabled = filters.allCategoriesEnabled
|
||||||
val allCategoriesEnabled = filters.allCategoriesEnabled
|
Column(
|
||||||
Box(modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.imePadding(), contentAlignment = Alignment.BottomCenter) {
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
Column(
|
.imePadding()
|
||||||
|
.height(50.dp)
|
||||||
|
) {
|
||||||
|
HorizontalDivider()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = filters.allowNetwork,
|
||||||
|
onClick = {
|
||||||
|
onFiltersChange(filters.copy(allowNetwork = !filters.allowNetwork))
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Language,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { Text(stringResource(R.string.search_filter_online)) }
|
||||||
|
)
|
||||||
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.height(36.dp)
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
)
|
||||||
HorizontalDivider()
|
FilterChip(
|
||||||
Row(
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
modifier = Modifier
|
selected = filters.apps && !allCategoriesEnabled,
|
||||||
.horizontalScroll(rememberScrollState())
|
onClick = {
|
||||||
.padding(horizontal = 8.dp),
|
onFiltersChange(filters.toggleApps())
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
},
|
||||||
) {
|
leadingIcon = {
|
||||||
FilterChip(
|
Icon(
|
||||||
selected = filters.allowNetwork,
|
imageVector = Icons.Rounded.Apps,
|
||||||
onClick = {
|
contentDescription = null,
|
||||||
onFiltersChange(filters.copy(allowNetwork = !filters.allowNetwork))
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Language,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = { Text(stringResource(R.string.search_filter_online)) }
|
|
||||||
)
|
)
|
||||||
VerticalDivider(
|
},
|
||||||
modifier = Modifier
|
label = { Text(stringResource(R.string.search_filter_apps)) }
|
||||||
.height(36.dp)
|
)
|
||||||
.padding(horizontal = 8.dp)
|
FilterChip(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
selected = filters.files && !allCategoriesEnabled,
|
||||||
|
onClick = {
|
||||||
|
onFiltersChange(filters.toggleFiles())
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Description,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_files)) }
|
||||||
selected = filters.apps && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleApps())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.contacts && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.toggleContacts())
|
||||||
imageVector = Icons.Rounded.Apps,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.Person,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.search_filter_apps)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_contacts)) }
|
||||||
selected = filters.files && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleFiles())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.events && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.toggleEvents())
|
||||||
imageVector = Icons.Rounded.Description,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.Today,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.preference_search_files)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_calendar)) }
|
||||||
selected = filters.contacts && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleContacts())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.shortcuts && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.toggleShortcuts())
|
||||||
imageVector = Icons.Rounded.Person,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.AppShortcut,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.preference_search_contacts)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_appshortcuts)) }
|
||||||
selected = filters.events && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleEvents())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.articles && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.toggleArticles())
|
||||||
imageVector = Icons.Rounded.Today,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.Wikipedia,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.preference_search_calendar)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_wikipedia)) }
|
||||||
selected = filters.shortcuts && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleShortcuts())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.websites && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.toggleWebsites())
|
||||||
imageVector = Icons.Rounded.AppShortcut,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.Public,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.preference_search_appshortcuts)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_websites)) }
|
||||||
selected = filters.articles && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleArticles())
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
},
|
selected = filters.places && !allCategoriesEnabled,
|
||||||
leadingIcon = {
|
onClick = {
|
||||||
Icon(
|
onFiltersChange(filters.togglePlaces())
|
||||||
imageVector = Icons.Rounded.Wikipedia,
|
},
|
||||||
contentDescription = null,
|
leadingIcon = {
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
Icon(
|
||||||
)
|
imageVector = Icons.Rounded.Place,
|
||||||
},
|
contentDescription = null,
|
||||||
label = { Text(stringResource(R.string.preference_search_wikipedia)) }
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.preference_search_locations)) }
|
||||||
selected = filters.websites && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
FilterChip(
|
||||||
onFiltersChange(filters.toggleWebsites())
|
selected = filters.tools && !allCategoriesEnabled,
|
||||||
},
|
onClick = {
|
||||||
leadingIcon = {
|
onFiltersChange(filters.toggleTools())
|
||||||
Icon(
|
},
|
||||||
imageVector = Icons.Rounded.Public,
|
leadingIcon = {
|
||||||
contentDescription = null,
|
Icon(
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
imageVector = Icons.Rounded.Handyman,
|
||||||
)
|
contentDescription = null,
|
||||||
},
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
label = { Text(stringResource(R.string.preference_search_websites)) }
|
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
label = { Text(stringResource(R.string.search_filter_tools)) }
|
||||||
selected = filters.places && !allCategoriesEnabled,
|
)
|
||||||
onClick = {
|
VerticalDivider(
|
||||||
onFiltersChange(filters.togglePlaces())
|
modifier = Modifier
|
||||||
},
|
.height(36.dp)
|
||||||
leadingIcon = {
|
.padding(horizontal = 8.dp)
|
||||||
Icon(
|
)
|
||||||
imageVector = Icons.Rounded.Place,
|
FilterChip(
|
||||||
contentDescription = null,
|
selected = filters.hiddenItems,
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
onClick = {
|
||||||
)
|
onFiltersChange(filters.copy(hiddenItems = !filters.hiddenItems))
|
||||||
},
|
},
|
||||||
label = { Text(stringResource(R.string.preference_search_locations)) }
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.VisibilityOff,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
)
|
)
|
||||||
FilterChip(
|
},
|
||||||
selected = filters.tools && !allCategoriesEnabled,
|
label = { Text(stringResource(R.string.preference_hidden_items)) }
|
||||||
onClick = {
|
)
|
||||||
onFiltersChange(filters.toggleTools())
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Handyman,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = { Text(stringResource(R.string.search_filter_tools)) }
|
|
||||||
)
|
|
||||||
VerticalDivider(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(36.dp)
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
)
|
|
||||||
FilterChip(
|
|
||||||
selected = filters.hiddenItems,
|
|
||||||
onClick = {
|
|
||||||
onFiltersChange(filters.copy(hiddenItems = !filters.hiddenItems))
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.VisibilityOff,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
label = { Text(stringResource(R.string.preference_hidden_items)) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
HorizontalDivider()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,12 +2,23 @@ package de.mm20.launcher2.ui.launcher.searchbar
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.scaleIn
|
import androidx.compose.animation.scaleIn
|
||||||
import androidx.compose.animation.scaleOut
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.imeAnimationTarget
|
||||||
import androidx.compose.foundation.layout.isImeVisible
|
import androidx.compose.foundation.layout.isImeVisible
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.text.KeyboardActionScope
|
import androidx.compose.foundation.text.KeyboardActionScope
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.FilterAlt
|
import androidx.compose.material.icons.rounded.FilterAlt
|
||||||
@ -24,7 +35,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||||
@ -47,8 +60,9 @@ fun LauncherSearchBar(
|
|||||||
actions: List<SearchAction>,
|
actions: List<SearchAction>,
|
||||||
highlightedAction: SearchAction?,
|
highlightedAction: SearchAction?,
|
||||||
isSearchOpen: Boolean = false,
|
isSearchOpen: Boolean = false,
|
||||||
reverse: Boolean = false,
|
|
||||||
darkColors: Boolean = false,
|
darkColors: Boolean = false,
|
||||||
|
bottomSearchBar: Boolean = false,
|
||||||
|
searchBarOffset: () -> Int = { 0 },
|
||||||
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
|
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
@ -61,64 +75,77 @@ fun LauncherSearchBar(
|
|||||||
else focusManager.clearFocus()
|
else focusManager.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSearchOpen && !searchVM.showFilters.value && WindowInsets.isImeVisible) {
|
|
||||||
KeyboardFilterBar(
|
|
||||||
filters = searchVM.filters.value,
|
|
||||||
onFiltersChange = {
|
|
||||||
searchVM.setFilters(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val _value = value()
|
val _value = value()
|
||||||
|
|
||||||
SearchBar(
|
Box(modifier = modifier) {
|
||||||
modifier = modifier,
|
SearchBar(
|
||||||
style = style, level = level(), value = _value, onValueChange = onValueChange,
|
modifier = Modifier
|
||||||
reverse = reverse,
|
.align(if (bottomSearchBar) Alignment.BottomCenter else Alignment.TopCenter)
|
||||||
darkColors = darkColors,
|
.windowInsetsPadding(WindowInsets.safeDrawing)
|
||||||
menu = {
|
.padding(8.dp)
|
||||||
AnimatedVisibility(
|
.offset { IntOffset(0, searchBarOffset()) },
|
||||||
isSearchOpen,
|
style = style, level = level(), value = _value, onValueChange = onValueChange,
|
||||||
enter = scaleIn(tween(100)),
|
reverse = bottomSearchBar,
|
||||||
exit = scaleOut(tween(100))
|
darkColors = darkColors,
|
||||||
) {
|
menu = {
|
||||||
FilledIconButton(
|
AnimatedVisibility(
|
||||||
onClick = {
|
isSearchOpen,
|
||||||
searchVM.showFilters.value = !searchVM.showFilters.value
|
enter = scaleIn(tween(100)),
|
||||||
},
|
exit = scaleOut(tween(100))
|
||||||
colors = if (searchVM.showFilters.value) IconButtonDefaults.filledTonalIconButtonColors()
|
|
||||||
else IconButtonDefaults.iconButtonColors()
|
|
||||||
) {
|
) {
|
||||||
Box {
|
FilledIconButton(
|
||||||
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = null)
|
onClick = {
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
searchVM.showFilters.value = !searchVM.showFilters.value
|
||||||
!searchVM.filters.value.allCategoriesEnabled,
|
},
|
||||||
enter = scaleIn(tween(100)),
|
colors = if (searchVM.showFilters.value) IconButtonDefaults.filledTonalIconButtonColors()
|
||||||
exit = scaleOut(tween(100)),
|
else IconButtonDefaults.iconButtonColors()
|
||||||
modifier = Modifier
|
) {
|
||||||
.align(Alignment.BottomEnd)
|
Box {
|
||||||
.offset(-3.dp, -3.dp)
|
Icon(imageVector = Icons.Rounded.FilterAlt, contentDescription = null)
|
||||||
) {
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
Badge(
|
!searchVM.filters.value.allCategoriesEnabled,
|
||||||
containerColor = MaterialTheme.colorScheme.tertiary,
|
enter = scaleIn(tween(100)),
|
||||||
)
|
exit = scaleOut(tween(100)),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.offset(-3.dp, -3.dp)
|
||||||
|
) {
|
||||||
|
Badge(
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
SearchBarMenu(searchBarValue = _value, onSearchBarValueChange = onValueChange)
|
||||||
SearchBarMenu(searchBarValue = _value, onSearchBarValueChange = onValueChange)
|
},
|
||||||
},
|
actions = {
|
||||||
actions = {
|
SearchBarActions(
|
||||||
SearchBarActions(
|
actions = actions,
|
||||||
actions = actions,
|
reverse = bottomSearchBar,
|
||||||
reverse = reverse,
|
highlightedAction = highlightedAction
|
||||||
highlightedAction = highlightedAction
|
)
|
||||||
|
},
|
||||||
|
focusRequester = focusRequester,
|
||||||
|
onFocus = { onFocusChange(true) },
|
||||||
|
onUnfocus = { onFocusChange(false) },
|
||||||
|
onKeyboardActionGo = onKeyboardActionGo
|
||||||
|
)
|
||||||
|
|
||||||
|
AnimatedVisibility (isSearchOpen && !searchVM.showFilters.value
|
||||||
|
// Use imeAnimationTarget instead of isImeVisible to animate the filter bar at the same time as the keyboard
|
||||||
|
&& WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current) > 0,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
modifier = Modifier.align(Alignment.BottomCenter)
|
||||||
|
) {
|
||||||
|
KeyboardFilterBar(
|
||||||
|
filters = searchVM.filters.value,
|
||||||
|
onFiltersChange = {
|
||||||
|
searchVM.setFilters(it)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
focusRequester = focusRequester,
|
}
|
||||||
onFocus = { onFocusChange(true) },
|
|
||||||
onUnfocus = { onFocusChange(false) },
|
|
||||||
onKeyboardActionGo = onKeyboardActionGo
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user